├── .github
├── CODEOWNERS
├── ISSUE_TEMPLATE
│ ├── 01-sdk-bug.yml
│ ├── 02-sdk-feature-request.yml
│ ├── 03-blank-issue.md
│ └── config.yml
├── dependabot.yml
├── policies
│ ├── msgraph-sdk-go-core-branch-protection.yml
│ └── resourceManagement.yml
├── pull_request_template.md
├── release-please.yml
└── workflows
│ ├── auto-merge-dependabot.yml
│ ├── codeql-analysis.yml
│ ├── go.yml
│ ├── project-auto-add.yml
│ └── sonarcloud.yml
├── .gitignore
├── .release-please-manifest.json
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── THIRD PARTY NOTICES
├── authentication
├── auzre_identity_access_token_provider_test.go
├── azure_identity_access_token_provider.go
├── azure_identity_authentication_provider.go
└── azure_identity_authentication_provider_test.go
├── batch_item_model.go
├── batch_request_collection.go
├── batch_request_collection_test.go
├── batch_request_test.go
├── batch_requests.go
├── batch_response_model.go
├── docs
└── batch_request.md
├── error_mappings_registry.go
├── error_mappings_registry_test.go
├── fileuploader
├── file_uploader_util.go
├── large_file_session.go
├── large_file_upload_task.go
├── large_file_upload_test.go
└── upload_slice.go
├── go.mod
├── go.sum
├── graph_client_factory.go
├── graph_client_options.go
├── graph_request_adapter_base.go
├── graph_telemetry_handler.go
├── graph_telemetry_handler_test.go
├── internal
├── errors.go
├── invalid_user_response.go
├── test_byte_stream.go
├── user.go
├── user_delta_response.go
└── user_response.go
├── page_iterator.go
├── page_iterator_test.go
├── release-please-config.json
├── sonar-project.properties
└── version.go
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @microsoftgraph/msgraph-devx-go-write
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/01-sdk-bug.yml:
--------------------------------------------------------------------------------
1 | name: SDK Bug Report
2 | description: File SDK bug report
3 | labels: ["type:bug", "status:waiting-for-triage"]
4 | body:
5 | - type: markdown
6 | attributes:
7 | value: |
8 | **Thank you for taking the time to fill out this bug report!**
9 | 💥Before submitting a new request, please search existing issues to see if an issue already exists.
10 | - type: textarea
11 | id: description
12 | attributes:
13 | label: Describe the bug
14 | description: |
15 | Provide a description of the actual behavior observed. If applicable please include any error messages, exception stacktraces or a screenshot.
16 | placeholder: I am trying to do [...] but [...]
17 | validations:
18 | required: true
19 | - type: textarea
20 | id: expected-behavior
21 | attributes:
22 | label: Expected behavior
23 | description: |
24 | A clear and concise description of what you expected to happen.
25 | placeholder: Expected behavior
26 | validations:
27 | required: true
28 | - type: textarea
29 | id: repro-steps
30 | attributes:
31 | label: How to reproduce
32 | description: |
33 | Please include minimal steps to reproduce the problem if possible. E.g.: the smallest possible code snippet; or steps to run project in link above. If possible include text as text rather than screenshots (so it shows up in searches).
34 | If there's a link to a public repo where the sample code exists, include it too.
35 | placeholder: Minimal Reproduction steps
36 | validations:
37 | required: true
38 | - type: input
39 | attributes:
40 | label: SDK Version
41 | placeholder: e.g. 5.32.1
42 | description: Version of the SDK with the bug described above.
43 | validations:
44 | required: false
45 | - type: input
46 | id: regression
47 | attributes:
48 | label: Latest version known to work for scenario above?
49 | description: |
50 | Did this work in a previous build or release of the SDK or API client? If you can try a previous release or build to find out, that can help us narrow down the problem. If you don't know, that's OK.
51 | placeholder: version-number
52 | validations:
53 | required: false
54 | - type: textarea
55 | id: known-workarounds
56 | attributes:
57 | label: Known Workarounds
58 | description: |
59 | Please provide a description of any known workarounds.
60 | placeholder: Known Workarounds
61 | validations:
62 | required: false
63 | - type: textarea
64 | id: logs
65 | attributes:
66 | label: Debug output
67 | description: Please copy and paste the debug output below.
68 | value: |
69 | Click to expand log
70 | ```
71 |
72 |
73 |
74 | ```
75 |
76 | validations:
77 | required: false
78 | - type: textarea
79 | id: configuration
80 | attributes:
81 | label: Configuration
82 | description: |
83 | Please provide more information on your SDK configuration:
84 | * What OS and version, and what distro if applicable (Windows 10, Windows 11, MacOS Catalina, Ubuntu 22.04)?
85 | * What is the architecture (x64, x86, ARM, ARM64)?
86 | * Do you know whether it is specific to that configuration?
87 | placeholder: |
88 | - OS:
89 | - architecture:
90 | validations:
91 | required: false
92 | - type: textarea
93 | id: other-info
94 | attributes:
95 | label: Other information
96 | description: |
97 | If you have an idea where the problem might lie, let us know that here. Please include any pointers to code, relevant changes, or related issues you know of.
98 | placeholder: Other information
99 | validations:
100 | required: false
101 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/02-sdk-feature-request.yml:
--------------------------------------------------------------------------------
1 | name: SDK Feature request
2 | description: Request a new feature on the SDK
3 | labels: ["type:feature", "status:waiting-for-triage"]
4 | body:
5 | - type: markdown
6 | attributes:
7 | value: |
8 | **Thank you for taking the time to fill out this feature request form!**
9 | 💥Please search to see if an issue already exists for the feature you are requesting.
10 | - type: textarea
11 | attributes:
12 | label: Is your feature request related to a problem? Please describe the problem.
13 | description: A clear and concise description of what the problem is.
14 | placeholder: I am trying to do [...] but [...]
15 | validations:
16 | required: false
17 | - type: textarea
18 | attributes:
19 | label: Describe the solution you'd like.
20 | description: |
21 | A clear and concise description of what you want to happen. Include any alternative solutions you've considered.
22 | validations:
23 | required: true
24 | - type: textarea
25 | attributes:
26 | label: Additional context?
27 | description: |
28 | Add any other context or screenshots about the feature request here.
29 | validations:
30 | required: false
31 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/03-blank-issue.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Blank issue
3 | about: Something that doesn't fit the other categories
4 | title: ''
5 | labels: ["status:waiting-for-triage"]
6 | assignees: ''
7 |
8 | ---
9 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: true
2 | contact_links:
3 | - name: Question on use of graph sdk
4 | url: https://github.com/microsoftgraph/msgraph-sdk-go-core/discussions
5 | about: Please add your question in the discussions section of the repo
6 | - name: Question on use of kiota
7 | url: https://github.com/microsoft/kiota/discussions
8 | about: Please add your question in the discussions section of the repo
9 | - name: Question or Feature Request for the MS Graph API?
10 | url: https://aka.ms/msgraphsupport
11 | about: Report an issue or limitation with the MS Graph service APIs
12 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: gomod
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | open-pull-requests-limit: 10
8 | labels:
9 | - Go
10 | - package-ecosystem: github-actions
11 | directory: "/"
12 | schedule:
13 | interval: daily
14 | open-pull-requests-limit: 10
--------------------------------------------------------------------------------
/.github/policies/msgraph-sdk-go-core-branch-protection.yml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Microsoft Corporation.
2 | # Licensed under the MIT License.
3 |
4 | # File initially created using https://github.com/MIchaelMainer/policyservicetoolkit/blob/main/branch_protection_export.ps1.
5 |
6 | name: msgraph-sdk-go-core-branch-protection
7 | description: Branch protection policy for the msgraph-sdk-go-core repository
8 | resource: repository
9 | configuration:
10 | branchProtectionRules:
11 |
12 | - branchNamePattern: main
13 | # This branch pattern applies to the following branches as of 06/12/2023 10:31:15:
14 | # main
15 |
16 | # Specifies whether this branch can be deleted. boolean
17 | allowsDeletions: false
18 | # Specifies whether forced pushes are allowed on this branch. boolean
19 | allowsForcePushes: false
20 | # Specifies whether new commits pushed to the matching branches dismiss pull request review approvals. boolean
21 | dismissStaleReviews: true
22 | # Specifies whether admins can overwrite branch protection. boolean
23 | isAdminEnforced: false
24 | # Indicates whether "Require a pull request before merging" is enabled. boolean
25 | requiresPullRequestBeforeMerging: true
26 | # Specifies the number of pull request reviews before merging. int (0-6). Should be null/empty if PRs are not required
27 | requiredApprovingReviewsCount: 1
28 | # Require review from Code Owners. Requires requiredApprovingReviewsCount. boolean
29 | requireCodeOwnersReview: true
30 | # Are commits required to be signed. boolean. TODO: all contributors must have commit signing on local machines.
31 | requiresCommitSignatures: false
32 | # Are conversations required to be resolved before merging? boolean
33 | requiresConversationResolution: true
34 | # Are merge commits prohibited from being pushed to this branch. boolean
35 | requiresLinearHistory: false
36 | # Required status checks to pass before merging. Values can be any string, but if the value does not correspond to any existing status check, the status check will be stuck on pending for status since nothing exists to push an actual status
37 | requiredStatusChecks:
38 | - build
39 | - SonarCloud
40 | - CodeQL
41 | # Require branches to be up to date before merging. Requires requiredStatusChecks. boolean
42 | requiresStrictStatusChecks: true
43 | # Indicates whether there are restrictions on who can push. boolean. Should be set with whoCanPush.
44 | restrictsPushes: false
45 | # Restrict who can dismiss pull request reviews. boolean
46 | restrictsReviewDismissals: false
47 |
48 |
--------------------------------------------------------------------------------
/.github/policies/resourceManagement.yml:
--------------------------------------------------------------------------------
1 | id:
2 | name: GitOps.PullRequestIssueManagement
3 | description: GitOps.PullRequestIssueManagement primitive
4 | owner:
5 | resource: repository
6 | disabled: false
7 | where:
8 | configuration:
9 | resourceManagementConfiguration:
10 | scheduledSearches:
11 | - description:
12 | frequencies:
13 | - hourly:
14 | hour: 6
15 | filters:
16 | - isIssue
17 | - isOpen
18 | - hasLabel:
19 | label: status:waiting-for-author-feedback
20 | - hasLabel:
21 | label: status no recent activity
22 | - noActivitySince:
23 | days: 3
24 | actions:
25 | - closeIssue
26 | - description:
27 | frequencies:
28 | - hourly:
29 | hour: 6
30 | filters:
31 | - isIssue
32 | - isOpen
33 | - hasLabel:
34 | label: status:waiting-for-author-feedback
35 | - noActivitySince:
36 | days: 4
37 | - isNotLabeledWith:
38 | label: status no recent activity
39 | actions:
40 | - addLabel:
41 | label: status no recent activity
42 | - addReply:
43 | reply: This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for **4 days**. It will be closed if no further activity occurs **within 3 days of this comment**.
44 | - description:
45 | frequencies:
46 | - hourly:
47 | hour: 6
48 | filters:
49 | - isIssue
50 | - isOpen
51 | - hasLabel:
52 | label: duplicate
53 | - noActivitySince:
54 | days: 1
55 | actions:
56 | - addReply:
57 | reply: This issue has been marked as duplicate and has not had any activity for **1 day**. It will be closed for housekeeping purposes.
58 | - closeIssue
59 | eventResponderTasks:
60 | - if:
61 | - payloadType: Issues
62 | - isAction:
63 | action: Closed
64 | - hasLabel:
65 | label: 'status:waiting-for-author-feedback'
66 | then:
67 | - removeLabel:
68 | label: 'status:waiting-for-author-feedback'
69 | description:
70 | - if:
71 | - payloadType: Issue_Comment
72 | - isAction:
73 | action: Created
74 | - isActivitySender:
75 | issueAuthor: True
76 | - hasLabel:
77 | label: status:waiting-for-author-feedback
78 | - isOpen
79 | then:
80 | - addLabel:
81 | label: 'needs attention :wave:'
82 | - removeLabel:
83 | label: status:waiting-for-author-feedback
84 | description:
85 | - if:
86 | - payloadType: Issues
87 | - not:
88 | isAction:
89 | action: Closed
90 | - hasLabel:
91 | label: status no recent activity
92 | then:
93 | - removeLabel:
94 | label: status no recent activity
95 | description:
96 | - if:
97 | - payloadType: Issue_Comment
98 | - hasLabel:
99 | label: status no recent activity
100 | then:
101 | - removeLabel:
102 | label: status no recent activity
103 | description:
104 | - if:
105 | - payloadType: Pull_Request
106 | then:
107 | - inPrLabel:
108 | label: 'Status: In PR'
109 | description:
110 | onFailure:
111 | onSuccess:
112 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## Overview
2 |
3 | Brief description of what this PR does
4 |
5 | ## Demo
6 | Optional. Screenshots, examples, etc.
7 |
8 | ## Notes
9 | Optional. Ancilliary topics, caveats, alternative strategies that didn't work out, anything else.
10 |
11 | ## Testing
12 | * How to test this PR
13 | * Prefer bulleted description
14 | * Start after checking out this branch
15 | * Include any setup required, such as bundling scripts, restarting services, etc.
16 | * Include test case and expected output
17 |
--------------------------------------------------------------------------------
/.github/release-please.yml:
--------------------------------------------------------------------------------
1 | manifest: true
2 | primaryBranch: main
3 | handleGHRelease: true
--------------------------------------------------------------------------------
/.github/workflows/auto-merge-dependabot.yml:
--------------------------------------------------------------------------------
1 | name: Auto-merge dependabot updates
2 |
3 | on:
4 | pull_request:
5 | branches: [ main ]
6 |
7 | permissions:
8 | pull-requests: write
9 | contents: write
10 |
11 | jobs:
12 |
13 | dependabot-merge:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | if: ${{ github.actor == 'dependabot[bot]' }}
18 |
19 | steps:
20 | - name: Dependabot metadata
21 | id: metadata
22 | uses: dependabot/fetch-metadata@v2.4.0
23 | with:
24 | github-token: "${{ secrets.GITHUB_TOKEN }}"
25 |
26 | - name: Enable auto-merge for Dependabot PRs
27 | # Only if version bump is not a major version change
28 | if: ${{steps.metadata.outputs.update-type != 'version-update:semver-major'}}
29 | run: gh pr merge --auto --merge "$PR_URL"
30 | env:
31 | PR_URL: ${{github.event.pull_request.html_url}}
32 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
33 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ main ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ main ]
20 | schedule:
21 | - cron: '40 16 * * 3'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 | permissions:
28 | actions: read
29 | contents: read
30 | security-events: write
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language: [ 'go' ]
36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support
38 |
39 | steps:
40 | - name: Checkout repository
41 | uses: actions/checkout@v4
42 |
43 | # Initializes the CodeQL tools for scanning.
44 | - name: Initialize CodeQL
45 | uses: github/codeql-action/init@v3
46 | with:
47 | languages: ${{ matrix.language }}
48 | # If you wish to specify custom queries, you can do so here or in a config file.
49 | # By default, queries listed here will override any specified in a config file.
50 | # Prefix the list here with "+" to use these queries and those in the config file.
51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
52 |
53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
54 | # If this step fails, then you should remove it and run the build manually (see below)
55 | - name: Autobuild
56 | uses: github/codeql-action/autobuild@v3
57 |
58 | # ℹ️ Command-line programs to run using the OS shell.
59 | # 📚 https://git.io/JvXDl
60 |
61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
62 | # and modify them (or add more) to build your code if your project
63 | # uses a compiled language
64 |
65 | #- run: |
66 | # make bootstrap
67 | # make release
68 |
69 | - name: Perform CodeQL Analysis
70 | uses: github/codeql-action/analyze@v3
71 |
--------------------------------------------------------------------------------
/.github/workflows/go.yml:
--------------------------------------------------------------------------------
1 | name: Go CI
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches: [ main ]
7 | pull_request:
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 | env:
13 | relativePath: ./
14 | steps:
15 | - uses: actions/checkout@v4
16 | with:
17 | submodules: recursive
18 | - uses: actions/setup-go@v5
19 | with:
20 | go-version: '^1.24'
21 | - name: Install dependencies
22 | run: go install
23 | working-directory: ${{ env.relativePath }}
24 | - name: Build SDK project
25 | run: go build
26 | working-directory: ${{ env.relativePath }}
27 | - name: Run unit tests
28 | run: go test ./...
29 | working-directory: ${{ env.relativePath }}
30 |
--------------------------------------------------------------------------------
/.github/workflows/project-auto-add.yml:
--------------------------------------------------------------------------------
1 | # This workflow is used to add new issues to GitHub GraphSDKs Project
2 |
3 | name: Add Issue or PR to project
4 | on:
5 | issues:
6 | types:
7 | - opened
8 | pull_request:
9 | types:
10 | - opened
11 | branches:
12 | - "main"
13 |
14 | jobs:
15 | track_issue:
16 | if: github.actor != 'dependabot[bot]' && github.event.pull_request.head.repo.fork == false
17 | runs-on: ubuntu-latest
18 | steps:
19 | - name: Generate token
20 | id: generate_token
21 | uses: actions/create-github-app-token@v2
22 | with:
23 | app-id: ${{ secrets.GRAPHBOT_APP_ID }}
24 | private-key: ${{ secrets.GRAPHBOT_APP_PEM }}
25 |
26 | - name: Get project data
27 | env:
28 | GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
29 | ORGANIZATION: microsoftgraph
30 | PROJECT_NUMBER: 55
31 | run: |
32 | gh api graphql -f query='
33 | query($org: String!, $number: Int!) {
34 | organization(login: $org){
35 | projectV2(number: $number) {
36 | id
37 | fields(first:20) {
38 | nodes {
39 | ... on ProjectV2SingleSelectField {
40 | id
41 | name
42 | options {
43 | id
44 | name
45 | }
46 | }
47 | }
48 | }
49 | }
50 | }
51 | }' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json
52 |
53 | echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV
54 | echo 'LANGUAGE_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Language") | .id' project_data.json) >> $GITHUB_ENV
55 | echo 'LANGUAGE_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Language") | .options[] | select(.name=="Go") |.id' project_data.json) >> $GITHUB_ENV
56 |
57 | - name: Add Issue or PR to project
58 | env:
59 | GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
60 | ISSUE_ID: ${{ github.event_name == 'issues' && github.event.issue.node_id || github.event.pull_request.node_id }}
61 | run: |
62 | item_id="$( gh api graphql -f query='
63 | mutation($project:ID!, $issue:ID!) {
64 | addProjectV2ItemById(input: {projectId: $project, contentId: $issue}) {
65 | item {
66 | id
67 | }
68 | }
69 | }' -f project=$PROJECT_ID -f issue=$ISSUE_ID --jq '.data.addProjectV2ItemById.item.id')"
70 |
71 | echo 'ITEM_ID='$item_id >> $GITHUB_ENV
72 |
73 | - name: Set Language
74 | env:
75 | GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
76 | run: |
77 | gh api graphql -f query='
78 | mutation (
79 | $project: ID!
80 | $item: ID!
81 | $language_field: ID!
82 | $language_value: String!
83 | ) {
84 | set_status: updateProjectV2ItemFieldValue(input: {
85 | projectId: $project
86 | itemId: $item
87 | fieldId: $language_field
88 | value: {singleSelectOptionId: $language_value}
89 | }) {
90 | projectV2Item {
91 | id
92 | }
93 | }
94 | }' -f project=$PROJECT_ID -f item=$ITEM_ID -f language_field=$LANGUAGE_FIELD_ID -f language_value=${{ env.LANGUAGE_OPTION_ID }} --silent
95 |
--------------------------------------------------------------------------------
/.github/workflows/sonarcloud.yml:
--------------------------------------------------------------------------------
1 | name: SonarCloud
2 | on:
3 | push:
4 | branches:
5 | - main
6 | pull_request:
7 | types: [opened, synchronize, reopened]
8 | jobs:
9 | sonarcloud:
10 | name: SonarCloud
11 | runs-on: ubuntu-latest
12 | if: ${{ !github.event.pull_request.head.repo.fork }}
13 | steps:
14 | - uses: actions/checkout@v4
15 | with:
16 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
17 | submodules: recursive
18 | - uses: actions/setup-go@v5
19 | with:
20 | go-version: '^1.24'
21 | - name: Install dependencies
22 | run: go install
23 | working-directory: ${{ env.relativePath }}
24 | - name: Build SDK project
25 | run: go build
26 | working-directory: ${{ env.relativePath }}
27 | - name: Run unit tests
28 | run: go test -coverprofile cover.out -coverpkg=./... ./...
29 | working-directory: ${{ env.relativePath }}
30 | - name: SonarCloud Scan
31 | uses: SonarSource/sonarqube-scan-action@v5
32 | env:
33 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, built with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
14 | # Dependency directories (remove the comment below to include it)
15 | # vendor/
16 |
17 | # Jetbrains files
18 | .idea/
19 |
--------------------------------------------------------------------------------
/.release-please-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | ".": "1.3.2"
3 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | ## [1.3.2](https://github.com/microsoftgraph/msgraph-sdk-go-core/compare/v1.3.1...v1.3.2) (2025-04-02)
6 |
7 |
8 | ### Bug Fixes
9 |
10 | * removes common go dependency ([5431c24](https://github.com/microsoftgraph/msgraph-sdk-go-core/commit/5431c242e5cee8130107c6978aa73d03ba1e08c6))
11 | * removes common go dependency ([e9d0a32](https://github.com/microsoftgraph/msgraph-sdk-go-core/commit/e9d0a3240562065ec3926841d03e7a29cd98d2fd))
12 |
13 | ## [1.3.1](https://github.com/microsoftgraph/msgraph-sdk-go-core/compare/v1.3.0...v1.3.1) (2025-03-24)
14 |
15 |
16 | ### Bug Fixes
17 |
18 | * upgrades common go dependency to solve triming issues ([3d78157](https://github.com/microsoftgraph/msgraph-sdk-go-core/commit/3d781577cb8e5776058df106d45a6b8e2731a11f))
19 | * upgrades common go dependency to solve triming issues ([f731ccc](https://github.com/microsoftgraph/msgraph-sdk-go-core/commit/f731ccc4e96fcfbdc2885f935bae4bf95b9c2900))
20 |
21 | ## [1.3.0](https://github.com/microsoftgraph/msgraph-sdk-go-core/compare/v1.2.1...v1.3.0) (2025-03-13)
22 |
23 |
24 | ### Features
25 |
26 | * upgrades required go version from go1.18 to go 1.22 ([6a25397](https://github.com/microsoftgraph/msgraph-sdk-go-core/commit/6a25397741b5b20ea899f2a5a4389dc130205168))
27 |
28 | ## [1.2.1](https://github.com/microsoftgraph/msgraph-sdk-go-core/compare/v1.2.0...v1.2.1) (2024-08-26)
29 |
30 |
31 | ### Bug Fixes
32 |
33 | * repeated slice uploading on large file upload task ([cb329cc](https://github.com/microsoftgraph/msgraph-sdk-go-core/commit/cb329cc395946a619cda5501da88dcda15d84d9b))
34 |
35 | ## [1.2.0](https://github.com/microsoftgraph/msgraph-sdk-go-core/compare/v1.1.0...v1.2.0) (2024-07-15)
36 |
37 |
38 | ### Features
39 |
40 | * add git release config ([69234a2](https://github.com/microsoftgraph/msgraph-sdk-go-core/commit/69234a236c1d212941e742593ce43d2a35a1212b))
41 |
42 |
43 | ### Bug Fixes
44 |
45 | * allows registration of page iterator headers ([#309](https://github.com/microsoftgraph/msgraph-sdk-go-core/issues/309)) ([d4b0806](https://github.com/microsoftgraph/msgraph-sdk-go-core/commit/d4b0806dadcc3ccdf07a8eca8ca7b93150094d7f))
46 | * content range order during upload ([#304](https://github.com/microsoftgraph/msgraph-sdk-go-core/issues/304)) ([f241e94](https://github.com/microsoftgraph/msgraph-sdk-go-core/commit/f241e947b28de38e8f7bc8c3d4eb6eb95b9afbdb))
47 |
48 | ## [1.1.0](https://github.com/microsoftgraph/msgraph-sdk-go-core/compare/v1.0.2...v1.1.0) (2024-07-10)
49 |
50 |
51 | ### Features
52 |
53 | * add git release config ([69234a2](https://github.com/microsoftgraph/msgraph-sdk-go-core/commit/69234a236c1d212941e742593ce43d2a35a1212b))
54 |
55 |
56 | ### Bug Fixes
57 |
58 | * content range order during upload ([#304](https://github.com/microsoftgraph/msgraph-sdk-go-core/issues/304)) ([f241e94](https://github.com/microsoftgraph/msgraph-sdk-go-core/commit/f241e947b28de38e8f7bc8c3d4eb6eb95b9afbdb))
59 |
60 | ## [1.1.0] - 2024-02-02
61 |
62 | ### Added
63 |
64 | - Added support for large file uploads.
65 |
66 | ## [1.0.2] - 2023-12-01
67 |
68 | ### Changed
69 |
70 | - Fixed a bug where GetBatchResponseById failed to deserialize error response bodies.
71 |
72 | ## [1.0.1] - 2023-11-24
73 |
74 | ### Changed
75 |
76 | - Fixed a bug where page iterator would panic if it couldn't find the GetValue method on the collection.
77 |
78 | ## [1.0.0] - 2023-05-04
79 |
80 | ### Changed
81 |
82 | - GA Release.
83 |
84 | ## [0.36.2] - 2023-05-01
85 |
86 | ### Added
87 |
88 | - `PageIterator` exposes `odata.nextLink` and `odata.deltaLink` of most recent page.
89 |
90 | ## [0.36.1] - 2023-04-17
91 |
92 | ### Added
93 |
94 | - Adds url token replacement to batch requests.
95 |
96 | ## [0.36.0] - 2023-03-27
97 |
98 | ### Added
99 |
100 | - Adds `BatchRequestCollection` support.
101 |
102 | ## [0.35.0] - 2023-03-23
103 |
104 | ### Added
105 |
106 | - `PageIterator` uses generics to define return type.
107 |
108 | ## [0.34.1] - 2023-03-06
109 |
110 | ### Changed
111 |
112 | - Change `PageIterator` to use `GetValue` method instead of `value` field to access response.
113 |
114 | ## [0.34.0] - 2023-02-23
115 |
116 | ### Added
117 |
118 | - Adds `UrlReplaceHandler` to default middleware.
119 |
120 | ## [0.33.1] - 2023-01-26
121 |
122 | ### Added
123 |
124 | - Upgrade dependencies to support backing store.
125 |
126 | ## [0.33.0] - 2023-01-17
127 |
128 | ### Added
129 |
130 | - Added authentication provider with Microsoft Graph defaults.
131 |
132 | ## [0.32.0] - 2023-01-11
133 |
134 | ### Changed
135 |
136 | - Upgraded abstractions and http dependencies.
137 |
138 | ## [0.31.1] - 2022-12-15
139 |
140 | ### Changed
141 |
142 | - Fixes path parameters missing when sending batch requests.
143 | - Fixes appending items when sending batch requests.
144 | - Fixes `Send` url when sending batch requests
145 |
146 | ## [0.31.0] - 2022-12-13
147 |
148 | ### Changed
149 |
150 | - Updated references to core libraries for multi-valued request headers.
151 |
152 | ## [0.30.1] - 2022-10-21
153 |
154 | ### Changed
155 |
156 | - Fix: Remove error swallowing in page iterator `fetchNextPage`.
157 |
158 | ## [0.30.0] - 2022-09-29
159 |
160 | ### Added
161 |
162 | - Adds ability to batch requests.
163 | - Adds tracing support via Open Telemetry.
164 |
165 | ## [0.29.0] - 2022-09-27
166 |
167 | ### Changed
168 |
169 | - Updated dependencies for additional serialization methods.
170 |
171 | ## [0.28.1] - 2022-09-09
172 |
173 | ### Changed
174 |
175 | - Updates references to kiota packages.
176 |
177 | ## [0.28.0] - 2022-08-24
178 |
179 | ### Changed
180 |
181 | - Upgrade to library `kiota-abstraction` breaking change
182 | - Introduces `context.Context` object to Page Iterator
183 |
184 | ## [0.27.0] - 2022-07-21
185 |
186 | ### Changed
187 |
188 | - Fixes PageIterator to use updated nextLink property
189 |
190 | ### Changed
191 |
192 | ## [0.26.2] - 2022-06-12
193 |
194 | ### Changed
195 |
196 | - Updated reference to kiota serialization json
197 | - Updated reference to kiota http
198 |
199 | ## [0.26.1] - 2022-06-07
200 |
201 | ### Changed
202 |
203 | - Updated references to kiota libraries and yaml dependencies.
204 |
205 | ## [0.26.0] - 2022-05-27
206 |
207 | ### Changed
208 |
209 | - Updated references to kiota libraries to add support for enum and enum collections responses.
210 |
211 | ## [0.25.1] - 2022-05-25
212 |
213 | ### Changed
214 |
215 | - Updated kiota http library reference.
216 |
217 | ## [0.25.0] - 2022-05-19
218 |
219 | ### Changed
220 |
221 | - Upgraded kiota dependencies for preliminary continuous access evaluation support.
222 |
223 | ## [0.24.0] - 2022-04-28
224 |
225 | ### Changed
226 |
227 | - Updated references to kiota libraries for request configuration revamp
228 |
229 | ## [0.23.0] - 2022-04-19
230 |
231 | ### Changed
232 |
233 | - Upgraded kiota libraries to address quote in url template issue.
234 | - Upgraded to go 18.
235 |
236 | ## [0.22.1] - 2022-04-14
237 |
238 | ### Changed
239 |
240 | - Fixed an issue with date serialization in JSON.
241 |
242 | ## [0.22.0] - 2022-04-12
243 |
244 | ### Changed
245 |
246 | - Updated references to kiota libraries for special character in parameter names support.
247 | - Breaking: removed the odata parameter names handler.
248 |
249 | ## [0.21.0] - 2022-04-06
250 |
251 | ### Changed
252 |
253 | - Updated reference to kiota libraries for deserialization simplification.
254 |
255 | ## [0.20.0] - 2022-03-31
256 |
257 | ### Changed
258 |
259 | - Updated reference to kiota libraries that were moved to their own repository.
260 |
261 | ## [0.0.17] - 2022-03-30
262 |
263 | ### Added
264 |
265 | - Added support for vendor specific content types
266 | - Added support for 204 no content responses
267 |
268 | ### Changed
269 |
270 | - Updated kiota libraries reference.
271 |
272 | ## [0.0.16] - 2022-03-21
273 |
274 | ### Changed
275 |
276 | - Breaking: updates PageIterator to receive a RequestAdapter interface instead of GraphRequestAdapterBase concrete type
277 | - Breaking: removed IsNil method from models
278 |
279 | ## [0.0.15] - 2022-03-15
280 |
281 | ### Changed
282 |
283 | - Updated references to kiota libraries for new supported types (byte, unit8, ...)
284 |
285 | ## [0.0.14] - 2022-03-11
286 |
287 | ### Changed
288 |
289 | - Publishes a version retraction for v0.11.0 that was wrongfully published and causes issues during upgrades
290 |
291 | ## [0.0.13] - 2022-03-04
292 |
293 | ### Changed
294 |
295 | - Breaking: updates kiota dependencies for parsable interface split.
296 |
297 | ## [0.0.12] - 2022-03-03
298 |
299 | ### Changed
300 |
301 | - Breaking: updates kiota dependencies to pass request information by reference and not by copy (request adapter, authentication provider).
302 |
303 | ## [0.0.11] - 2022-03-02
304 |
305 | ### Changed
306 |
307 | - Breaking: updates kiota dependencies references to prepare for type discriminator support.
308 |
309 | ## [0.0.10] - 2022-02-28
310 |
311 | ### Changed
312 |
313 | - Fixed a bug where http client configuration would impact the default client configuration for other usages.
314 |
315 | ## [0.0.9] - 2022-02-16
316 |
317 | ### Added
318 |
319 | - Added support for deserializing error responses (will return error)
320 |
321 | ### Changed
322 |
323 | - Fixed a bug where response body compression would send empty bodies
324 |
325 | ## [0.0.8] - 2022-02-08
326 |
327 | ### Added
328 |
329 | - Added support for request body compression (gzip)
330 | - Added support for response body decompression (gzip)
331 |
332 | ### Changed
333 |
334 | - Fixes a bug where resuming the page iterator wouldn't work
335 | - Fixes a bug where OData query parameters would be added twice in some cases
336 |
337 | ## [0.0.7] - 2022-02-03
338 |
339 | ### Changed
340 |
341 | - Updated references to Kiota packages to fix a [bug where the access token would never be attached to the request](https://github.com/microsoft/kiota/pull/1116).
342 |
343 | ## [0.0.6] - 2022-02-02
344 |
345 | ### Added
346 |
347 | - Adds missing delta token for OData query parameters dollar sign injection.
348 | - Adds PageIterator task
349 |
350 | ## [0.0.5] - 2021-12-02
351 |
352 | ### Changed
353 |
354 | - Fixes a bug where the middleware pipeline would run only on the first request of the client/adapter/http client.
355 |
356 | ## [0.0.4] - 2021-12-01
357 |
358 | ### Changed
359 |
360 | - Adds the missing github.com/microsoft/kiota/authentication/go/azure dependency
361 |
362 | ## [0.0.3] - 2021-11-30
363 |
364 | ### Changed
365 |
366 | - Updated dependencies and switched to Go 17.
367 |
368 | ## [0.0.2] - 2021-11-08
369 |
370 | ### Changed
371 |
372 | - Updated kiota abstractions and http to provide support for setting the base URL
373 |
374 | ## [0.0.1] - 2021-10-22
375 |
376 | ### Added
377 |
378 | - Initial release
379 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Microsoft Open Source Code of Conduct
2 |
3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
4 |
5 | Resources:
6 |
7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
10 | - Employees can reach out at [aka.ms/opensource/moderation-support](https://aka.ms/opensource/moderation-support)
11 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to the Microsoft Graph Core SDK for Go
2 |
3 | The Microsoft Graph Core SDK for Go is available for all manner of contribution. There are a couple of different recommended paths to get contributions into the released version of this SDK.
4 |
5 | __NOTE__ A signed a contribution license agreement is required for all contributions, and is checked automatically on new pull requests. Please read and sign [the agreement](https://cla.microsoft.com/) before starting any work for this repository.
6 |
7 | ## File issues
8 |
9 | The best way to get started with a contribution is to start a dialog with the owners of this repository. Sometimes features will be under development or out of scope for this SDK and it's best to check before starting work on contribution.
10 |
11 | ## Submit pull requests for trivial changes
12 |
13 | If you are making a change that does not affect the interface components and does not affect other downstream callers, feel free to make a pull request against the __dev__ branch. The dev branch will be updated frequently.
14 |
15 | Revisions of this nature will result in a 0.0.X change of the version number.
16 |
17 | ## Submit pull requests for features
18 |
19 | If major functionality is being added, or there will need to be gestation time for a change, it should be submitted against the __feature__ branch.
20 |
21 | Revisions of this nature will result in a 0.X.X change of the version number.
22 |
23 | ## Commit message format
24 |
25 | To support our automated release process, pull requests are required to follow the [Conventional Commit](https://www.conventionalcommits.org/en/v1.0.0/)
26 | format.
27 |
28 | Each commit message consists of a **header**, an optional **body** and an optional **footer**. The header is the first line of the commit and
29 | MUST have a **type** (see below for a list of types) and a **description**. An optional **scope** can be added to the header to give extra context.
30 |
31 | ```
32 | [optional scope]:
33 |
34 |
35 |
36 |
37 | ```
38 |
39 | The recommended commit types used are:
40 |
41 | - **feat** for feature updates (increments the _minor_ version)
42 | - **fix** for bug fixes (increments the _patch_ version)
43 | - **perf** for performance related changes e.g. optimizing an algorithm
44 | - **refactor** for code refactoring changes
45 | - **test** for test suite updates e.g. adding a test or fixing a test
46 | - **style** for changes that don't affect the meaning of code. e.g. formatting changes
47 | - **docs** for documentation updates e.g. ReadMe update or code documentation updates
48 | - **build** for build system changes (gradle updates, external dependency updates)
49 | - **ci** for CI configuration file changes e.g. updating a pipeline
50 | - **chore** for miscallaneous non-sdk changesin the repo e.g. removing an unused file
51 |
52 | Adding a footer with the prefix **BREAKING CHANGE:** will cause an increment of the _major_ version.
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Microsoft Graph
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Microsoft Graph Core SDK for Go
2 |
3 | [](https://pkg.go.dev/github.com/microsoftgraph/msgraph-sdk-go-core/) [](https://sonarcloud.io/dashboard?id=microsoftgraph_msgraph-sdk-go-core) [](https://sonarcloud.io/dashboard?id=microsoftgraph_msgraph-sdk-go-core)
4 |
5 | Get started with the Microsoft Graph Core SDK for Go by integrating the [Microsoft Graph API](https://docs.microsoft.com/graph/overview) into your Go application! You can also have a look at the [Go documentation](https://pkg.go.dev/github.com/microsoftgraph/msgraph-sdk-go-core/)
6 |
7 | > **Note:** Although you can use this library directly, we recommend you use the [v1](https://github.com/microsoftgraph/msgraph-sdk-go) or [beta](https://github.com/microsoftgraph/msgraph-beta-sdk-go) library which rely on this library and additionally provide a fluent style Go API and models.
8 | >
9 | > **Note:** The Microsoft Graph Go SDK is currently in Release Candidate (RC) version starting from version 0.34.1. The SDK is still undergoing testing but minimum breaking changes should be expected. Checkout the [known limitations](https://github.com/microsoftgraph/msgraph-sdk-go-core/issues/1).
10 |
11 | ## Samples and usage guide
12 |
13 | - [Middleware usage](https://github.com/microsoftgraph/msgraph-sdk-design/)
14 |
15 | ## 1. Installation
16 |
17 | ```Shell
18 | go get github.com/microsoftgraph/msgraph-sdk-go-core
19 | go get github.com/Azure/azure-sdk-for-go/sdk/azidentity
20 | ```
21 |
22 | ## 2. Getting started
23 |
24 | ### 2.1 Register your application
25 |
26 | Register your application by following the steps at [Register your app with the Microsoft Identity Platform](https://docs.microsoft.com/graph/auth-register-app-v2).
27 |
28 | ### 2.2 Create an AuthenticationProvider object
29 |
30 | An instance of the **GraphRequestAdapterBase** class handles building client. To create a new instance of this class, you need to provide an instance of **AuthenticationProvider**, which can authenticate requests to Microsoft Graph.
31 |
32 | For an example of how to get an authentication provider, see [choose a Microsoft Graph authentication provider](https://docs.microsoft.com/graph/sdks/choose-authentication-providers?tabs=Go).
33 |
34 | > Note: we are working to add the getting started information for Go to our public documentation, in the meantime the following sample should help you getting started.
35 |
36 | ```Golang
37 | import (
38 | azidentity "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
39 | a "github.com/microsoftgraph/msgraph-sdk-go-core/authentication"
40 | "context"
41 | )
42 |
43 | cred, err := azidentity.NewDeviceCodeCredential(&azidentity.DeviceCodeCredentialOptions{
44 | TenantID: "",
45 | ClientID: "",
46 | UserPrompt: func(ctx context.Context, message azidentity.DeviceCodeMessage) error {
47 | fmt.Println(message.Message)
48 | return nil
49 | },
50 | })
51 |
52 | if err != nil {
53 | fmt.Printf("Error creating credentials: %v\n", err)
54 | }
55 |
56 | auth, err := a.NewAzureIdentityAuthenticationProviderWithScopes(cred, []string{"Mail.Read", "Mail.Send"})
57 | if err != nil {
58 | fmt.Printf("Error authentication provider: %v\n", err)
59 | return
60 | }
61 |
62 | ```
63 |
64 | ### 2.3 Get a Request Adapter object
65 |
66 | You must get a **GraphRequestAdapterBase** object to make requests against the service.
67 |
68 | ```Golang
69 | import core "github.com/microsoftgraph/msgraph-sdk-go-core"
70 |
71 | adapter, err := core.NewGraphRequestAdapterBase(auth)
72 | if err != nil {
73 | fmt.Printf("Error creating adapter: %v\n", err)
74 | return
75 | }
76 | ```
77 |
78 | ## 3. Make requests against the service
79 |
80 | After you have a **GraphRequestAdapterBase** that is authenticated, you can begin making calls against the service. The requests against the service look like our [REST API](https://docs.microsoft.com/graph/api/overview?view=graph-rest-1.0).
81 |
82 | ### 3.1 Get the user's details
83 |
84 | To retrieve the user's details
85 |
86 | ```Golang
87 | import abs "github.com/microsoft/kiota-abstractions-go"
88 |
89 | requestInf := abs.NewRequestInformation()
90 | targetUrl, err := url.Parse("https://graph.microsoft.com/v1.0/me")
91 | if err != nil {
92 | fmt.Printf("Error parsing URL: %v\n", err)
93 | }
94 | requestInf.SetUri(*targetUrl)
95 |
96 | // User is your own type that implements Parsable or comes from the service library
97 | user, err := adapter.SendAsync(*requestInf, func() { return &User }, nil)
98 |
99 | if err != nil {
100 | fmt.Printf("Error getting the user: %v\n", err)
101 | }
102 | ```
103 |
104 | ## 4. Issues
105 |
106 | For known issues, see [issues](https://github.com/MicrosoftGraph/msgraph-sdk-go-core/issues).
107 |
108 | ## 5. Contributions
109 |
110 | The Microsoft Graph SDK is open for contribution. To contribute to this project, see [Contributing](https://github.com/microsoftgraph/msgraph-sdk-go-core/blob/main/CONTRIBUTING.md).
111 |
112 | ## 6. License
113 |
114 | Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the [MIT license](LICENSE).
115 |
116 | ## 7. Third-party notices
117 |
118 | [Third-party notices](THIRD%20PARTY%20NOTICES)
119 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Security
4 |
5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
6 |
7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.
8 |
9 | ## Reporting Security Issues
10 |
11 | **Please do not report security vulnerabilities through public GitHub issues.**
12 |
13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
14 |
15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
16 |
17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
18 |
19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
20 |
21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
22 | * Full paths of source file(s) related to the manifestation of the issue
23 | * The location of the affected source code (tag/branch/commit or direct URL)
24 | * Any special configuration required to reproduce the issue
25 | * Step-by-step instructions to reproduce the issue
26 | * Proof-of-concept or exploit code (if possible)
27 | * Impact of the issue, including how an attacker might exploit the issue
28 |
29 | This information will help us triage your report more quickly.
30 |
31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.
32 |
33 | ## Preferred Languages
34 |
35 | We prefer all communications to be in English.
36 |
37 | ## Policy
38 |
39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
40 |
41 |
42 |
--------------------------------------------------------------------------------
/THIRD PARTY NOTICES:
--------------------------------------------------------------------------------
1 | This file is based on or incorporates material from the projects listed below
2 | (Third Party IP). The original copyright notice and the license under which
3 | Microsoft received such Third Party IP, are set forth below. Such licenses and
4 | notices are provided for informational purposes only. Microsoft licenses the
5 | Third Party IP to you under the licensing terms for the Microsoft product.
6 | Microsoft reserves all other rights not expressly granted under this agreement,
7 | whether by implication, estoppel or otherwise.
8 |
9 |
--------------------------------------------------------------------------------
/authentication/auzre_identity_access_token_provider_test.go:
--------------------------------------------------------------------------------
1 | package authentication
2 |
3 | import (
4 | "testing"
5 |
6 | absauth "github.com/microsoft/kiota-abstractions-go/authentication"
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestAccessTokenProviderImplementsInterface(t *testing.T) {
11 | var value absauth.AccessTokenProvider = &AzureIdentityAccessTokenProvider{}
12 | assert.NotNil(t, value)
13 | }
14 |
--------------------------------------------------------------------------------
/authentication/azure_identity_access_token_provider.go:
--------------------------------------------------------------------------------
1 | package authentication
2 |
3 | import (
4 | azcore "github.com/Azure/azure-sdk-for-go/sdk/azcore"
5 | kiotaidentity "github.com/microsoft/kiota-authentication-azure-go"
6 | )
7 |
8 | // AzureIdentityAccessTokenProvider is a wrapper around the AzureIdentityAccessTokenProvider from the Kiota library with Microsoft Graph default valid hosts.
9 | type AzureIdentityAccessTokenProvider struct {
10 | kiotaidentity.AzureIdentityAccessTokenProvider
11 | }
12 |
13 | // NewAzureIdentityAccessTokenProvider creates a new instance of the AzureIdentityAccessTokenProvider using ":///.default" as the default scope.
14 | func NewAzureIdentityAccessTokenProvider(credential azcore.TokenCredential) (*AzureIdentityAccessTokenProvider, error) {
15 | return NewAzureIdentityAccessTokenProviderWithScopes(credential, nil)
16 | }
17 |
18 | // NewAzureIdentityAccessTokenProviderWithScopes creates a new instance of the AzureIdentityAccessTokenProvider.
19 | func NewAzureIdentityAccessTokenProviderWithScopes(credential azcore.TokenCredential, scopes []string) (*AzureIdentityAccessTokenProvider, error) {
20 | return NewAzureIdentityAccessTokenProviderWithScopesAndValidHosts(credential, scopes, nil)
21 | }
22 |
23 | // NewAzureIdentityAccessTokenProviderWithScopesAndValidHosts creates a new instance of the AzureIdentityAccessTokenProvider.
24 | func NewAzureIdentityAccessTokenProviderWithScopesAndValidHosts(credential azcore.TokenCredential, scopes []string, validHosts []string) (*AzureIdentityAccessTokenProvider, error) {
25 | return NewAzureIdentityAccessTokenProviderWithScopesAndValidHostsAndObservabilityOptions(credential, scopes, validHosts, kiotaidentity.ObservabilityOptions{})
26 | }
27 |
28 | // NewAzureIdentityAccessTokenProviderWithScopesAndValidHosts creates a new instance of the AzureIdentityAccessTokenProvider.
29 | func NewAzureIdentityAccessTokenProviderWithScopesAndValidHostsAndObservabilityOptions(credential azcore.TokenCredential, scopes []string, validHosts []string, observabilityOptions kiotaidentity.ObservabilityOptions) (*AzureIdentityAccessTokenProvider, error) {
30 | base, err := kiotaidentity.NewAzureIdentityAccessTokenProviderWithScopesAndValidHostsAndObservabilityOptions(credential, scopes, validHosts, observabilityOptions)
31 | if err != nil {
32 | return nil, err
33 | }
34 | if len(validHosts) == 0 {
35 | base.GetAllowedHostsValidator().SetAllowedHosts([]string{"graph.microsoft.com", "graph.microsoft.us", "dod-graph.microsoft.us", "graph.microsoft.de", "microsoftgraph.chinacloudapi.cn", "canary.graph.microsoft.com"})
36 | }
37 | result := &AzureIdentityAccessTokenProvider{
38 | AzureIdentityAccessTokenProvider: *base,
39 | }
40 |
41 | return result, nil
42 | }
43 |
--------------------------------------------------------------------------------
/authentication/azure_identity_authentication_provider.go:
--------------------------------------------------------------------------------
1 | package authentication
2 |
3 | import (
4 | azcore "github.com/Azure/azure-sdk-for-go/sdk/azcore"
5 | absauth "github.com/microsoft/kiota-abstractions-go/authentication"
6 | kiotaidentity "github.com/microsoft/kiota-authentication-azure-go"
7 | )
8 |
9 | // AzureIdentityAuthenticationProvider is a wrapper around the AzureIdentityAuthenticationProvider that sets default values for Microsoft Graph.
10 | type AzureIdentityAuthenticationProvider struct {
11 | kiotaidentity.AzureIdentityAuthenticationProvider
12 | }
13 |
14 | // NewAzureIdentityAuthenticationProvider creates a new instance of the AzureIdentityAuthenticationProvider using "https://graph.microsoft.com/.default" as the default scope.
15 | func NewAzureIdentityAuthenticationProvider(credential azcore.TokenCredential) (*AzureIdentityAuthenticationProvider, error) {
16 | return NewAzureIdentityAuthenticationProviderWithScopes(credential, nil)
17 | }
18 |
19 | // NewAzureIdentityAuthenticationProviderWithScopes creates a new instance of the AzureIdentityAuthenticationProvider.
20 | func NewAzureIdentityAuthenticationProviderWithScopes(credential azcore.TokenCredential, scopes []string) (*AzureIdentityAuthenticationProvider, error) {
21 | return NewAzureIdentityAuthenticationProviderWithScopesAndValidHosts(credential, scopes, nil)
22 | }
23 |
24 | // NewAzureIdentityAuthenticationProviderWithScopesAndValidHosts creates a new instance of the AzureIdentityAuthenticationProvider.
25 | func NewAzureIdentityAuthenticationProviderWithScopesAndValidHosts(credential azcore.TokenCredential, scopes []string, validHosts []string) (*AzureIdentityAuthenticationProvider, error) {
26 | return NewAzureIdentityAuthenticationProviderWithScopesAndValidHostsAndObservabilityOptions(credential, scopes, validHosts, kiotaidentity.ObservabilityOptions{})
27 | }
28 |
29 | // NewAzureIdentityAuthenticationProviderWithScopesAndValidHostsAndObservabilityOptions creates a new instance of the AzureIdentityAuthenticationProvider.
30 | func NewAzureIdentityAuthenticationProviderWithScopesAndValidHostsAndObservabilityOptions(credential azcore.TokenCredential, scopes []string, validHosts []string, observabilityOptions kiotaidentity.ObservabilityOptions) (*AzureIdentityAuthenticationProvider, error) {
31 | accessTokenProvider, err := NewAzureIdentityAccessTokenProviderWithScopesAndValidHostsAndObservabilityOptions(credential, scopes, validHosts, observabilityOptions)
32 | if err != nil {
33 | return nil, err
34 | }
35 | baseBearer := absauth.NewBaseBearerTokenAuthenticationProvider(accessTokenProvider)
36 | result := &AzureIdentityAuthenticationProvider{
37 | AzureIdentityAuthenticationProvider: kiotaidentity.AzureIdentityAuthenticationProvider{
38 | BaseBearerTokenAuthenticationProvider: *baseBearer,
39 | },
40 | }
41 | return result, nil
42 | }
43 |
--------------------------------------------------------------------------------
/authentication/azure_identity_authentication_provider_test.go:
--------------------------------------------------------------------------------
1 | package authentication
2 |
3 | import (
4 | "testing"
5 |
6 | absauth "github.com/microsoft/kiota-abstractions-go/authentication"
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestAuthenticationProviderImplementsInterface(t *testing.T) {
11 | var value absauth.AuthenticationProvider = &AzureIdentityAuthenticationProvider{}
12 | assert.NotNil(t, value)
13 | }
14 |
--------------------------------------------------------------------------------
/batch_item_model.go:
--------------------------------------------------------------------------------
1 | package msgraphgocore
2 |
3 | import (
4 | "errors"
5 | abs "github.com/microsoft/kiota-abstractions-go"
6 | "github.com/microsoft/kiota-abstractions-go/serialization"
7 | jsonserialization "github.com/microsoft/kiota-serialization-json-go"
8 | "reflect"
9 | )
10 |
11 | // BatchItem is an instance of the BatchRequest payload to be later serialized to a json payload
12 | type BatchItem interface {
13 | serialization.Parsable
14 | GetId() *string
15 | SetId(value *string)
16 | GetMethod() *string
17 | SetMethod(value *string)
18 | GetUrl() *string
19 | SetUrl(value *string)
20 | GetHeaders() RequestHeader
21 | SetHeaders(value RequestHeader)
22 | GetBody() RequestBody
23 | SetBody(value RequestBody)
24 | GetDependsOn() []string
25 | SetDependsOn(value []string)
26 | GetStatus() *int32
27 | SetStatus(value *int32)
28 | DependsOnItem(item BatchItem)
29 | }
30 |
31 | type batchItem struct {
32 | Id *string
33 | method *string
34 | Url *string
35 | Headers RequestHeader
36 | Body RequestBody
37 | DependsOn []string
38 | Status *int32
39 | }
40 |
41 | // DependsOnItem creates a dependency chain between BatchItems.If A depends on B, then B will be sent before B
42 | // A batchItem can only depend on one other batchItem
43 | // see: https://docs.microsoft.com/en-us/graph/known-issues#request-dependencies-are-limited
44 | func (bi *batchItem) DependsOnItem(item BatchItem) {
45 | dependsOn := append(item.GetDependsOn(), *item.GetId())
46 | bi.SetDependsOn(dependsOn)
47 | }
48 |
49 | // NewBatchItem creates an instance of BatchItem
50 | func NewBatchItem() BatchItem {
51 | return &batchItem{
52 | DependsOn: make([]string, 0),
53 | }
54 | }
55 |
56 | // GetId returns batch item `id` property
57 | func (bi *batchItem) GetId() *string {
58 | return bi.Id
59 | }
60 |
61 | // SetId sets string value as batch item `id` property
62 | func (bi *batchItem) SetId(value *string) {
63 | bi.Id = value
64 | }
65 |
66 | // GetMethod returns batch item `Method` property
67 | func (bi *batchItem) GetMethod() *string {
68 | return bi.method
69 | }
70 |
71 | // SetMethod sets string value as batch item `Method` property
72 | func (bi *batchItem) SetMethod(value *string) {
73 | bi.method = value
74 | }
75 |
76 | // GetUrl returns batch item `Url` property
77 | func (bi *batchItem) GetUrl() *string {
78 | return bi.Url
79 | }
80 |
81 | // SetUrl sets string value as batch item `Url` property
82 | func (bi *batchItem) SetUrl(value *string) {
83 | bi.Url = value
84 | }
85 |
86 | // GetHeaders returns batch item `Header` as a map[string]string
87 | func (bi *batchItem) GetHeaders() RequestHeader {
88 | return bi.Headers
89 | }
90 |
91 | // SetHeaders sets map[string]string value as batch item `Header` property
92 | func (bi *batchItem) SetHeaders(value RequestHeader) {
93 | bi.Headers = value
94 | }
95 |
96 | // GetBody returns batch item `RequestBody` property
97 | func (bi *batchItem) GetBody() RequestBody {
98 | return bi.Body
99 | }
100 |
101 | // SetBody sets map[string]string value as batch item `RequestBody` property
102 | func (bi *batchItem) SetBody(value RequestBody) {
103 | bi.Body = value
104 | }
105 |
106 | // GetDependsOn returns batch item `dependsOn` property as a string array
107 | func (bi *batchItem) GetDependsOn() []string {
108 | return bi.DependsOn
109 | }
110 |
111 | // SetDependsOn sets []string value as batch item `dependsOn` property
112 | func (bi *batchItem) SetDependsOn(value []string) {
113 | bi.DependsOn = value
114 | }
115 |
116 | // GetStatus returns batch item `status` property
117 | func (bi *batchItem) GetStatus() *int32 {
118 | return bi.Status
119 | }
120 |
121 | // SetStatus sets int32 value as batch item `int` property
122 | func (bi *batchItem) SetStatus(value *int32) {
123 | bi.Status = value
124 | }
125 |
126 | // Serialize serializes information the current object
127 | func (bi *batchItem) Serialize(writer serialization.SerializationWriter) error {
128 | {
129 | err := writer.WriteStringValue("id", bi.GetId())
130 | if err != nil {
131 | return err
132 | }
133 | }
134 | {
135 | err := writer.WriteStringValue("method", bi.GetMethod())
136 | if err != nil {
137 | return err
138 | }
139 | }
140 | {
141 | err := writer.WriteStringValue("url", bi.GetUrl())
142 | if err != nil {
143 | return err
144 | }
145 | }
146 | {
147 | err := writer.WriteAnyValue("headers", bi.GetHeaders())
148 | if err != nil {
149 | return err
150 | }
151 | }
152 | {
153 | err := writer.WriteAnyValue("body", bi.GetBody())
154 | if err != nil {
155 | return err
156 | }
157 | }
158 | {
159 | err := writer.WriteCollectionOfStringValues("dependsOn", bi.GetDependsOn())
160 | if err != nil {
161 | return err
162 | }
163 | }
164 | {
165 | err := writer.WriteInt32Value("status", bi.GetStatus())
166 | if err != nil {
167 | return err
168 | }
169 | }
170 | return nil
171 | }
172 |
173 | // GetFieldDeserializers the deserialization information for the current model
174 | func (bi *batchItem) GetFieldDeserializers() map[string]func(serialization.ParseNode) error {
175 | res := make(map[string]func(serialization.ParseNode) error)
176 | res["id"] = abs.SetStringValue(bi.SetId)
177 | res["method"] = abs.SetStringValue(bi.SetMethod)
178 | res["url"] = abs.SetStringValue(bi.SetUrl)
179 | res["headers"] = func(n serialization.ParseNode) error {
180 | rawVal, err := n.GetRawValue()
181 | if err != nil {
182 | return err
183 | }
184 |
185 | if rawVal == nil {
186 | return nil
187 | }
188 |
189 | result, err := castMapOfStrings(rawVal)
190 | if err != nil {
191 | return err
192 | }
193 |
194 | bi.SetHeaders(result)
195 | return nil
196 | }
197 | res["body"] = func(n serialization.ParseNode) error {
198 | rawVal, err := n.GetRawValue()
199 | if err != nil {
200 | return err
201 | }
202 |
203 | if rawVal == nil {
204 | return nil
205 | }
206 |
207 | result, err := convertToMap(rawVal)
208 | if err != nil {
209 | return err
210 | }
211 |
212 | bi.SetBody(result)
213 | return nil
214 | }
215 | res["dependsOn"] = abs.SetCollectionOfPrimitiveValues("string", bi.SetDependsOn)
216 | res["status"] = abs.SetInt32Value(bi.SetStatus)
217 | return res
218 | }
219 |
220 | func convertToMap(rawVal interface{}) (map[string]interface{}, error) {
221 | kind := reflect.ValueOf(rawVal)
222 | if kind.Kind() == reflect.Map {
223 | result := make(map[string]interface{})
224 | err := deserializeMapped(kind, result)
225 | if err != nil {
226 | return nil, err
227 | }
228 |
229 | return result, nil
230 | }
231 | return nil, errors.New("interface was not a map")
232 | }
233 |
234 | func deserializeNode(value serialization.ParseNode) (interface{}, error) {
235 | rawVal, err := value.GetRawValue()
236 | if err != nil {
237 | return nil, err
238 | } else {
239 | kind := reflect.ValueOf(rawVal)
240 | if kind.Kind() == reflect.Map {
241 |
242 | result := make(map[string]interface{})
243 | err := deserializeMapped(kind, result)
244 | if err != nil {
245 | return nil, err
246 | }
247 | return result, nil
248 | } else {
249 | return deserializeValue(rawVal)
250 | }
251 | }
252 | }
253 |
254 | func deserializeMapped(v reflect.Value, result map[string]interface{}) error {
255 | for _, key := range v.MapKeys() {
256 | value, err := deserializeValue(v.MapIndex(key).Interface())
257 | if err != nil {
258 | return err
259 | } else {
260 | result[key.String()] = value
261 | }
262 | }
263 | return nil
264 | }
265 |
266 | func deserializeNodes(value []*jsonserialization.JsonParseNode) (interface{}, error) {
267 | slice := make([]interface{}, len(value))
268 | for index, element := range value {
269 | res, err := deserializeNode(element)
270 | if err != nil {
271 | return nil, err
272 | }
273 | slice[index] = res
274 | }
275 | return slice, nil
276 | }
277 |
278 | func deserializeValue(value interface{}) (interface{}, error) {
279 | switch v := value.(type) {
280 | case int:
281 | case float64:
282 | case string:
283 | return value, nil
284 | case *int:
285 | case *float64:
286 | case *string:
287 | return value, nil
288 | case jsonserialization.JsonParseNode:
289 | case *jsonserialization.JsonParseNode:
290 | return deserializeNode(v)
291 | case []*jsonserialization.JsonParseNode:
292 | return deserializeNodes(v)
293 | case []jsonserialization.JsonParseNode:
294 | return deserializeNodes(abs.CollectionApply(v, func(x jsonserialization.JsonParseNode) *jsonserialization.JsonParseNode {
295 | return &x
296 | }))
297 | default:
298 | return value, nil
299 | }
300 | return nil, nil
301 | }
302 |
303 | func castMapOfStrings(rawVal interface{}) (map[string]string, error) {
304 | result := make(map[string]string)
305 | v := reflect.ValueOf(rawVal)
306 | if v.Kind() == reflect.Map {
307 | for _, key := range v.MapKeys() {
308 | val, err := deserializeValue(v.MapIndex(key).Interface())
309 | if err != nil {
310 | return nil, err
311 | }
312 | result[key.String()] = *(val.(*string))
313 | }
314 | }
315 | return result, nil
316 | }
317 |
318 | // CreateBatchRequestItemDiscriminator creates a new instance of the appropriate class based on discriminator value
319 | func CreateBatchRequestItemDiscriminator(serialization.ParseNode) (serialization.Parsable, error) {
320 | var res batchItem
321 | return &res, nil
322 | }
323 |
--------------------------------------------------------------------------------
/batch_request_collection.go:
--------------------------------------------------------------------------------
1 | package msgraphgocore
2 |
3 | import (
4 | "context"
5 | "errors"
6 | abstractions "github.com/microsoft/kiota-abstractions-go"
7 | )
8 |
9 | type BatchRequestCollection struct {
10 | batchRequest *batchRequest
11 | batchLimit int
12 | }
13 |
14 | const MaxBatchRequests = 4
15 |
16 | // NewBatchRequestCollection creates an instance of a BatchRequestCollection with a default request limit
17 | func NewBatchRequestCollection(adapter abstractions.RequestAdapter) *BatchRequestCollection {
18 | return NewBatchRequestCollectionWithLimit(adapter, MaxBatchRequests)
19 | }
20 |
21 | // NewBatchRequestCollectionWithLimit creates an instance of a BatchRequestCollection with a defined limit in requests
22 | func NewBatchRequestCollectionWithLimit(adapter abstractions.RequestAdapter, batchLimit int) *BatchRequestCollection {
23 | return &BatchRequestCollection{
24 | batchRequest: &batchRequest{
25 | adapter: adapter,
26 | },
27 | batchLimit: batchLimit,
28 | }
29 | }
30 |
31 | // AddBatchRequestStep converts RequestInformation to a BatchItem and adds it to a BatchRequestCollection
32 | func (b *BatchRequestCollection) AddBatchRequestStep(reqInfo abstractions.RequestInformation) (BatchItem, error) {
33 | return b.batchRequest.addLimitedBatchRequestStep(reqInfo, -1)
34 | }
35 |
36 | // Send serializes and sends the batch request to the server
37 | func (b *BatchRequestCollection) Send(ctx context.Context, adapter abstractions.RequestAdapter) (BatchResponse, error) {
38 | // spit request with a max of 19
39 | requestItems := chunkSlice(b.batchRequest.requests, 19)
40 |
41 | if len(requestItems) > b.batchLimit {
42 | return nil, errors.New("exceeded max number of batch requests")
43 | }
44 |
45 | // execute requests
46 | response := NewBatchResponse()
47 | for _, requests := range requestItems {
48 | batch := NewBatchRequest(b.batchRequest.adapter)
49 | batch.SetRequests(requests)
50 | res, err := batch.Send(ctx, adapter)
51 | if err != nil {
52 | return nil, err
53 | }
54 | response.AddResponses(res.GetResponses())
55 | }
56 |
57 | return response, nil
58 | }
59 |
60 | func chunkSlice[T interface{}](slice []T, chunkSize int) [][]T {
61 | var chunks [][]T
62 | for i := 0; i < len(slice); i += chunkSize {
63 | end := i + chunkSize
64 | if end > len(slice) {
65 | end = len(slice)
66 | }
67 |
68 | chunks = append(chunks, slice[i:end])
69 | }
70 | return chunks
71 | }
72 |
--------------------------------------------------------------------------------
/batch_request_collection_test.go:
--------------------------------------------------------------------------------
1 | package msgraphgocore
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/stretchr/testify/assert"
7 | "github.com/stretchr/testify/require"
8 | "net/http"
9 | "net/http/httptest"
10 | "testing"
11 | )
12 |
13 | func TestNewBatchRequestCollectionNoLimit(t *testing.T) {
14 | batch := NewBatchRequestCollection(reqAdapter)
15 | reqInfo := getRequestInfo()
16 |
17 | for i := 0; i < 20; i++ {
18 | _, err := batch.AddBatchRequestStep(*reqInfo)
19 | if err != nil {
20 | return
21 | }
22 | }
23 |
24 | _, err := batch.AddBatchRequestStep(*reqInfo)
25 | assert.Nil(t, err)
26 | }
27 |
28 | func TestBatchRequestCollectionReturnsBatchResponse(t *testing.T) {
29 | testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
30 | w.Header().Set("Content-Type", "application/json")
31 | jsonResponse := getDummyJSON()
32 | w.WriteHeader(200)
33 | fmt.Fprint(w, jsonResponse)
34 | }))
35 | defer testServer.Close()
36 |
37 | reqInfo := getRequestInfo()
38 |
39 | mockPath := testServer.URL + "/$batch"
40 | reqAdapter.SetBaseUrl(mockPath) // check that path is not empty instead
41 |
42 | batch := NewBatchRequestCollection(reqAdapter)
43 | for i := 0; i < 40; i++ {
44 | _, err := batch.AddBatchRequestStep(*reqInfo)
45 | if err != nil {
46 | require.NoError(t, err)
47 | }
48 | }
49 |
50 | resp, err := batch.Send(context.Background(), reqAdapter)
51 | require.NoError(t, err)
52 |
53 | assert.Equal(t, len(resp.GetResponses()), 12)
54 | }
55 |
56 | func TestBatchRequestResponseGetFailedResponses(t *testing.T) {
57 | testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
58 | w.Header().Set("Content-Type", "application/json")
59 | jsonResponse := getDummyJSON()
60 | w.WriteHeader(200)
61 | fmt.Fprint(w, jsonResponse)
62 | }))
63 | defer testServer.Close()
64 |
65 | reqInfo := getRequestInfo()
66 |
67 | mockPath := testServer.URL + "/$batch"
68 | reqAdapter.SetBaseUrl(mockPath) // check that path is not empty instead
69 |
70 | batch := NewBatchRequestCollection(reqAdapter)
71 | _, err := batch.AddBatchRequestStep(*reqInfo)
72 | require.NoError(t, err)
73 |
74 | resp, err := batch.Send(context.Background(), reqAdapter)
75 | require.NoError(t, err)
76 |
77 | assert.Equal(t, len(resp.GetStatusCodes()), 4)
78 |
79 | status := resp.GetFailedResponses()
80 | assert.Equal(t, 1, len(status))
81 | }
82 |
--------------------------------------------------------------------------------
/batch_request_test.go:
--------------------------------------------------------------------------------
1 | package msgraphgocore
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 | "net/http"
8 | "net/http/httptest"
9 | "net/url"
10 | "testing"
11 |
12 | "github.com/microsoft/kiota-abstractions-go/serialization"
13 | "github.com/microsoftgraph/msgraph-sdk-go-core/internal"
14 |
15 | abstractions "github.com/microsoft/kiota-abstractions-go"
16 | "github.com/stretchr/testify/assert"
17 | "github.com/stretchr/testify/require"
18 | )
19 |
20 | func p[T interface{}](t T) *T {
21 | return &t
22 | }
23 |
24 | func TestConstructionOfRequests(t *testing.T) {
25 | reqInfo := getRequestInfo()
26 |
27 | batch := NewBatchRequest(reqAdapter)
28 |
29 | item1, err := batch.AddBatchRequestStep(*reqInfo)
30 | require.NoError(t, err)
31 |
32 | item2, err := batch.AddBatchRequestStep(*reqInfo)
33 | require.NoError(t, err)
34 |
35 | assert.Equal(t, len(batch.GetRequests()), 2)
36 | assert.Equal(t, batch.GetRequests()[0], item1)
37 | assert.Equal(t, batch.GetRequests()[1], item2)
38 | }
39 |
40 | func TestRegisteringDependsOn(t *testing.T) {
41 |
42 | reqInfo1 := getRequestInfo()
43 | reqInfo2 := getRequestInfo()
44 |
45 | batch := NewBatchRequest(reqAdapter)
46 | batchItem1, err := batch.AddBatchRequestStep(*reqInfo1)
47 | require.NoError(t, err)
48 |
49 | batchItem2, err := batch.AddBatchRequestStep(*reqInfo2)
50 | require.NoError(t, err)
51 |
52 | batchItem2.DependsOnItem(batchItem1)
53 |
54 | assert.Equal(t, batchItem2.GetDependsOn(), []string{*batchItem1.GetId()})
55 | }
56 |
57 | func TestReturnsBatchResponse(t *testing.T) {
58 | testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
59 | w.Header().Set("Content-Type", "application/json")
60 | jsonResponse := getDummyJSON()
61 | w.WriteHeader(200)
62 | fmt.Fprint(w, jsonResponse)
63 | }))
64 | defer testServer.Close()
65 |
66 | reqInfo := getRequestInfo()
67 |
68 | mockPath := testServer.URL + "/$batch"
69 | reqAdapter.SetBaseUrl(mockPath) // check that path is not empty instead
70 |
71 | batch := NewBatchRequest(reqAdapter)
72 | _, err := batch.AddBatchRequestStep(*reqInfo)
73 | require.NoError(t, err)
74 |
75 | resp, err := batch.Send(context.Background(), reqAdapter)
76 | require.NoError(t, err)
77 |
78 | assert.Equal(t, len(resp.GetResponses()), 4)
79 | }
80 |
81 | func TestContentSentToServer(t *testing.T) {
82 | testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
83 | w.Header().Set("Content-Type", "application/json")
84 | jsonResponse := getDummyJSON()
85 | w.WriteHeader(200)
86 | fmt.Fprint(w, jsonResponse)
87 | }))
88 | defer testServer.Close()
89 |
90 | reqInfo := getRequestInfo()
91 |
92 | mockPath := testServer.URL + "/$batch"
93 | reqAdapter.SetBaseUrl(mockPath) // check that path is not empty instead
94 |
95 | batch := NewBatchRequest(reqAdapter)
96 | item, err := batch.AddBatchRequestStep(*reqInfo)
97 | item.SetId(p("123"))
98 | require.NoError(t, err)
99 |
100 | baseUrl, err := getBaseUrl(reqAdapter)
101 | require.NoError(t, err)
102 |
103 | requestInfo, err := buildRequestInfo(context.Background(), reqAdapter, batch, baseUrl)
104 | require.NoError(t, err)
105 | content := string(requestInfo.Content)
106 | expected := "{\"requests\":[{\"id\":\"123\",\"method\":\"GET\",\"url\":\"\",\"headers\":{\"content-type\":\"application/json\"},\"body\":{\"username\":\"name\"},\"dependsOn\":[]}]}"
107 | assert.Equal(t, expected, content)
108 |
109 | resp, err := batch.Send(context.Background(), reqAdapter)
110 | require.NoError(t, err)
111 |
112 | assert.Equal(t, len(resp.GetResponses()), 4)
113 | }
114 |
115 | func TestRespectsBatchItemLimitOf20BatchItems(t *testing.T) {
116 | batch := NewBatchRequest(reqAdapter)
117 | reqInfo := getRequestInfo()
118 |
119 | for i := 0; i < 20; i++ {
120 | _, err := batch.AddBatchRequestStep(*reqInfo)
121 | if err != nil {
122 | return
123 | }
124 | }
125 |
126 | _, err := batch.AddBatchRequestStep(*reqInfo)
127 | assert.Equal(t, err.Error(), "batch items limit exceeded. BatchRequest has a limit of 20 batch items")
128 | }
129 |
130 | func TestHandlesUnhandledHTTPError(t *testing.T) {
131 | testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
132 | w.Header().Set("Content-Type", "application/json")
133 | w.WriteHeader(403)
134 | fmt.Fprint(w, "")
135 | }))
136 | defer testServer.Close()
137 |
138 | mockPath := testServer.URL + "/$batch"
139 | reqAdapter.SetBaseUrl(mockPath)
140 |
141 | reqInfo := getRequestInfo()
142 | batch := NewBatchRequest(reqAdapter)
143 | _, err := batch.AddBatchRequestStep(*reqInfo)
144 | require.NoError(t, err)
145 |
146 | _, err = batch.Send(context.Background(), reqAdapter)
147 | assert.Equal(t, err.Error(), "The server returned an unexpected status code and no error factory is registered for this code: 403")
148 | }
149 |
150 | func TestHandlesHTTPError(t *testing.T) {
151 | testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
152 | w.Header().Set("Content-Type", "application/json")
153 | w.WriteHeader(403)
154 | fmt.Fprint(w, "{}")
155 | }))
156 | defer testServer.Close()
157 |
158 | mockPath := testServer.URL + "/$batch"
159 | reqAdapter.SetBaseUrl(mockPath)
160 |
161 | reqInfo := getRequestInfo()
162 | batch := NewBatchRequest(reqAdapter)
163 | _, err := batch.AddBatchRequestStep(*reqInfo)
164 | require.NoError(t, err)
165 |
166 | errorMapping := abstractions.ErrorMappings{
167 | "4XX": internal.CreateSampleErrorFromDiscriminatorValue,
168 | "5XX": internal.CreateSampleErrorFromDiscriminatorValue,
169 | }
170 | // register errorMapper
171 | err = RegisterError(BatchRequestErrorRegistryKey, errorMapping)
172 | require.NoError(t, err)
173 |
174 | _, err = batch.Send(context.Background(), reqAdapter)
175 |
176 | var sampleError *internal.SampleError
177 | switch {
178 | case errors.As(err, &sampleError):
179 | assert.Equal(t, "error status code received from the API", err.Error())
180 | default:
181 | assert.Fail(t, "error type is not as expected")
182 | }
183 |
184 | err = DeRegisterError(BatchRequestErrorRegistryKey)
185 | require.NoError(t, err)
186 | }
187 |
188 | func TestGetResponseByIdForSuccessfulRequest(t *testing.T) {
189 | mockResponse := `{
190 | "responses": [
191 | {
192 | "id": "2",
193 | "status": 200,
194 | "body": {
195 | "username": "testuser",
196 | "person" : {
197 | "firstName" : "Tony",
198 | "lastName" : "Blair",
199 | "active" : false,
200 | "bankBalance": 234234.67,
201 | "accounts" : [1,2,3],
202 | "positions" : ["Prime","Minister"],
203 | "children" : [
204 | {
205 | "firstName" : "Kathryn",
206 | "lastName" : "Blair"
207 | },
208 | {
209 | "firstName" : "Euan",
210 | "lastName" : "Blair"
211 | }
212 | ]
213 | }
214 | }
215 | }
216 | ]
217 | }`
218 | mockServer := makeMockRequest(200, mockResponse)
219 | defer mockServer.Close()
220 |
221 | mockPath := mockServer.URL + "/$batch"
222 | reqAdapter.SetBaseUrl(mockPath)
223 |
224 | reqInfo := getRequestInfo()
225 | batch := NewBatchRequest(reqAdapter)
226 | _, err := batch.AddBatchRequestStep(*reqInfo)
227 | if err != nil {
228 | return
229 | }
230 |
231 | resp, err := batch.Send(context.Background(), reqAdapter)
232 | require.NoError(t, err)
233 |
234 | user, err := GetBatchResponseById[Userable](resp, "2", CreateUser)
235 | require.NoError(t, err)
236 |
237 | assert.Equal(t, *(user.GetUserName()), "testuser")
238 | }
239 |
240 | type Userable interface {
241 | serialization.Parsable
242 | GetUserName() *string
243 | SetUserName(*string)
244 | }
245 |
246 | type User struct {
247 | UserName *string
248 | Person *Person
249 | }
250 |
251 | func (u *User) GetUserName() *string {
252 | return u.UserName
253 | }
254 |
255 | func (u *User) SetUserName(userName *string) {
256 | u.UserName = userName
257 | }
258 |
259 | func (u *User) Serialize(writer serialization.SerializationWriter) error {
260 | panic("implement me")
261 | }
262 |
263 | func (u *User) GetFieldDeserializers() map[string]func(serialization.ParseNode) error {
264 | res := make(map[string]func(serialization.ParseNode) error)
265 | res["username"] = func(n serialization.ParseNode) error {
266 | val, err := n.GetStringValue()
267 | if err != nil {
268 | return err
269 | }
270 | if val != nil {
271 | u.SetUserName(val)
272 | }
273 | return nil
274 | }
275 | return res
276 | }
277 |
278 | func CreateUser(parseNode serialization.ParseNode) (serialization.Parsable, error) {
279 | return &User{}, nil
280 | }
281 |
282 | type Person struct {
283 | FirstName string `json:"firstName"`
284 | LastName string `json:"lastName"`
285 | Active bool `json:"active"`
286 | Positions []*string `json:"positions"`
287 | BankBalance *float64 `json:"bankBalance"`
288 | Accounts []*int `json:"accounts"`
289 | Children []*Person `json:"children"`
290 | }
291 |
292 | func (u Person) Serialize(writer serialization.SerializationWriter) error {
293 | return nil
294 | }
295 |
296 | func (u Person) GetFieldDeserializers() map[string]func(serialization.ParseNode) error {
297 | return make(map[string]func(serialization.ParseNode) error)
298 | }
299 |
300 | func TestGetResponseByIdFailedRequest(t *testing.T) {
301 | mockServer := makeMockRequest(200, getDummyJSON())
302 | defer mockServer.Close()
303 |
304 | mockPath := mockServer.URL + "/$batch"
305 | reqAdapter.SetBaseUrl(mockPath)
306 |
307 | reqInfo := getRequestInfo()
308 | batch := NewBatchRequest(reqAdapter)
309 | _, err := batch.AddBatchRequestStep(*reqInfo)
310 | require.NoError(t, err)
311 |
312 | resp, err := batch.Send(context.Background(), reqAdapter)
313 | require.NoError(t, err)
314 |
315 | _, err = GetBatchResponseById[Userable](resp, "3", CreateUser)
316 | assert.Equal(t, "The server returned an unexpected status code and no error factory is registered for this code: 401", err.Error())
317 | }
318 |
319 | func TestGetErrorResponseBodyById(t *testing.T) {
320 | var jsonBlob = `{
321 | "responses": [{
322 | "id": "3",
323 | "status": 400,
324 | "headers": {
325 | "Content-Type": "application/json"
326 | },
327 | "body": {
328 | "error": {
329 | "code": "ExtensionError",
330 | "message": "Exception: [Status Code: BadRequest; Reason: Boom]",
331 | "innerError": {
332 | "request-id": "123"
333 | }
334 | }
335 | }
336 | }]
337 | }`
338 |
339 | errorMapping := abstractions.ErrorMappings{
340 | "4XX": internal.CreateSampleErrorFromDiscriminatorValue,
341 | "5XX": internal.CreateSampleErrorFromDiscriminatorValue,
342 | }
343 | err := RegisterError("Userable", errorMapping)
344 | assert.NoError(t, err)
345 |
346 | mockServer := makeMockRequest(200, jsonBlob)
347 | defer mockServer.Close()
348 |
349 | mockPath := mockServer.URL + "/$batch"
350 | reqAdapter.SetBaseUrl(mockPath)
351 |
352 | reqInfo := getRequestInfo()
353 | batch := NewBatchRequest(reqAdapter)
354 | _, err = batch.AddBatchRequestStep(*reqInfo)
355 | require.NoError(t, err)
356 |
357 | resp, err := batch.Send(context.Background(), reqAdapter)
358 | require.NoError(t, err)
359 |
360 | _, err = GetBatchResponseById[Userable](resp, "3", CreateUser)
361 | serr := &internal.SampleError{}
362 | assert.ErrorAs(t, err, &serr)
363 | assert.Equal(t, "Exception: [Status Code: BadRequest; Reason: Boom]", serr.Message)
364 |
365 | err = DeRegisterError("Userable")
366 | require.NoError(t, err)
367 | }
368 |
369 | func TestGetResponseByIdFailedRequestWithFactory(t *testing.T) {
370 | mockServer := makeMockRequest(200, getDummyJSON())
371 | defer mockServer.Close()
372 |
373 | mockPath := mockServer.URL + "/$batch"
374 | reqAdapter.SetBaseUrl(mockPath)
375 |
376 | errorMapping := abstractions.ErrorMappings{
377 | "4XX": internal.CreateSampleErrorFromDiscriminatorValue,
378 | "5XX": internal.CreateSampleErrorFromDiscriminatorValue,
379 | }
380 | // register errorMapper
381 | err := RegisterError(BatchRequestErrorRegistryKey, errorMapping)
382 | require.NoError(t, err)
383 |
384 | reqInfo := getRequestInfo()
385 | batch := NewBatchRequest(reqAdapter)
386 | _, err = batch.AddBatchRequestStep(*reqInfo)
387 | require.NoError(t, err)
388 |
389 | resp, err := batch.Send(context.Background(), reqAdapter)
390 | require.NoError(t, err)
391 |
392 | _, err = GetBatchResponseById[Userable](resp, "3", CreateUser)
393 | assert.Equal(t, "The server returned an unexpected status code with no response body: 401", err.Error())
394 |
395 | err = DeRegisterError(BatchRequestErrorRegistryKey)
396 | require.NoError(t, err)
397 | }
398 |
399 | func makeMockRequest(mockStatus int, mockResponse string) *httptest.Server {
400 | return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
401 | w.Header().Set("Content-Type", "application/json")
402 | w.WriteHeader(mockStatus)
403 | fmt.Fprint(w, mockResponse)
404 | }))
405 | }
406 |
407 | func getRequestInfo() *abstractions.RequestInformation {
408 | content := `
409 | {
410 | "username": "name"
411 | }
412 | `
413 | reqInfo := abstractions.NewRequestInformation()
414 | reqInfo.SetUri(url.URL{})
415 | reqInfo.Content = []byte(content)
416 | reqInfo.UrlTemplate = "{+baseurl}/$batch"
417 | headers := abstractions.NewRequestHeaders()
418 | headers.Add("Content-Type", "application/json")
419 | reqInfo.Headers.AddAll(headers)
420 |
421 | return reqInfo
422 | }
423 |
424 | func getDummyJSON() string {
425 | return `{
426 | "responses": [
427 | {
428 | "id": "1",
429 | "status": 302,
430 | "body": null,
431 | "headers": {
432 | "location": "https://b0mpua-by3301.files.1drv.com/y23vmagahszhxzlcvhasdhasghasodfi"
433 | }
434 | },
435 | {
436 | "id": "3",
437 | "status": 401,
438 | "body": {
439 | "error": {
440 | "code": "Forbidden",
441 | "message": "Insufficient permissions"
442 | }
443 | }
444 | },
445 | {
446 | "id": "2",
447 | "status": 200,
448 | "body": {
449 | "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(microsoft.graph.plannerTask)",
450 | "value": []
451 | }
452 | },
453 | {
454 | "id": "4",
455 | "status": 204,
456 | "url": "https://graph.microsoft.com/v1.0/$metadata#Collection(microsoft.graph.plannerTask)",
457 | "body": null
458 | }
459 | ]
460 | }`
461 | }
462 |
--------------------------------------------------------------------------------
/batch_requests.go:
--------------------------------------------------------------------------------
1 | package msgraphgocore
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "encoding/gob"
7 | "encoding/json"
8 | "errors"
9 | "net/url"
10 | "reflect"
11 | "strconv"
12 | "strings"
13 |
14 | "github.com/google/uuid"
15 | abs "github.com/microsoft/kiota-abstractions-go"
16 | abstractions "github.com/microsoft/kiota-abstractions-go"
17 | "github.com/microsoft/kiota-abstractions-go/serialization"
18 | absser "github.com/microsoft/kiota-abstractions-go/serialization"
19 | nethttplibrary "github.com/microsoft/kiota-http-go"
20 | )
21 |
22 | const BatchRequestErrorRegistryKey = "BATCH_REQUEST_ERROR_REGISTRY_KEY"
23 | const jsonContentType = "application/json"
24 |
25 | // RequestHeader is a type alias for http request headers
26 | type RequestHeader map[string]string
27 |
28 | // Serialize serializes information the current object
29 | func (br RequestHeader) Serialize(writer serialization.SerializationWriter) error {
30 | return nil
31 | }
32 |
33 | // GetFieldDeserializers the deserialization information for the current model
34 | func (br RequestHeader) GetFieldDeserializers() map[string]func(serialization.ParseNode) error {
35 | return make(map[string]func(serialization.ParseNode) error)
36 | }
37 |
38 | // RequestBody is a type alias for http request bodies
39 | type RequestBody map[string]interface{}
40 |
41 | // Serialize serializes information the current object
42 | func (br RequestBody) Serialize(writer serialization.SerializationWriter) error {
43 | return nil
44 | }
45 |
46 | // GetFieldDeserializers the deserialization information for the current model
47 | func (br RequestBody) GetFieldDeserializers() map[string]func(serialization.ParseNode) error {
48 | return make(map[string]func(serialization.ParseNode) error)
49 | }
50 |
51 | type batchRequest struct {
52 | requests []BatchItem
53 | adapter abstractions.RequestAdapter
54 | }
55 |
56 | // NewBatchRequest creates an instance of BatchRequest
57 | func NewBatchRequest(adapter abstractions.RequestAdapter) BatchRequest {
58 | return &batchRequest{
59 | adapter: adapter,
60 | }
61 | }
62 |
63 | // BatchRequest models all the properties of a batch request
64 | type BatchRequest interface {
65 | serialization.Parsable
66 | GetRequests() []BatchItem
67 | SetRequests(requests []BatchItem)
68 | AddBatchRequestStep(reqInfo abstractions.RequestInformation) (BatchItem, error)
69 | Send(ctx context.Context, adapter abstractions.RequestAdapter) (BatchResponse, error)
70 | }
71 |
72 | // GetRequests return all the Items in the batch request
73 | func (br *batchRequest) GetRequests() []BatchItem {
74 | return br.requests
75 | }
76 |
77 | // SetRequests add a collection of requests to the batch Items
78 | func (br *batchRequest) SetRequests(requests []BatchItem) {
79 | br.requests = requests
80 | }
81 |
82 | // Serialize serializes information the current object
83 | func (br *batchRequest) Serialize(writer serialization.SerializationWriter) error {
84 | {
85 | cast := abs.CollectionApply(br.requests, func(v BatchItem) serialization.Parsable {
86 | return v.(serialization.Parsable)
87 | })
88 | err := writer.WriteCollectionOfObjectValues("requests", cast)
89 | if err != nil {
90 | return err
91 | }
92 | }
93 | return nil
94 | }
95 |
96 | // GetFieldDeserializers the deserialization information for the current model
97 | func (br *batchRequest) GetFieldDeserializers() map[string]func(serialization.ParseNode) error {
98 | return make(map[string]func(serialization.ParseNode) error)
99 | }
100 |
101 | // AddBatchRequestStep converts RequestInformation to a BatchItem and adds it to a BatchRequest
102 | //
103 | // You can add upto 20 BatchItems to a BatchRequest
104 | func (br *batchRequest) AddBatchRequestStep(reqInfo abstractions.RequestInformation) (BatchItem, error) {
105 | return br.addLimitedBatchRequestStep(reqInfo, 19)
106 | }
107 |
108 | func (br *batchRequest) addLimitedBatchRequestStep(reqInfo abstractions.RequestInformation, requestLimit int) (BatchItem, error) {
109 | if requestLimit != -1 && len(br.GetRequests()) > requestLimit {
110 | return nil, errors.New("batch items limit exceeded. BatchRequest has a limit of 20 batch items")
111 | }
112 |
113 | batchItem, err := br.toBatchItem(reqInfo)
114 | if err != nil {
115 | return nil, err
116 | }
117 |
118 | br.SetRequests(append(br.GetRequests(), batchItem))
119 | return batchItem, nil
120 | }
121 |
122 | func (br *batchRequest) toBatchItem(requestInfo abstractions.RequestInformation) (BatchItem, error) {
123 | if _, ok := requestInfo.PathParameters["baseurl"]; !ok {
124 | // address issue for request information missing baseUrl
125 | // https://github.com/microsoft/kiota/issues/2061
126 | requestInfo.PathParameters["baseurl"] = br.adapter.GetBaseUrl()
127 | }
128 |
129 | uri, err := requestInfo.GetUri()
130 | if err != nil {
131 | return nil, err
132 | }
133 | uriString := nethttplibrary.ReplacePathTokens(uri.String(), ReplacementPairs)
134 |
135 | var body map[string]interface{}
136 | if requestInfo.Content != nil {
137 | err = json.Unmarshal(requestInfo.Content, &body)
138 | if err != nil {
139 | return nil, err
140 | }
141 | }
142 |
143 | newID := uuid.NewString()
144 | method := requestInfo.Method.String()
145 |
146 | request := NewBatchItem()
147 | request.SetId(&newID)
148 | request.SetMethod(&method)
149 | request.SetBody(body)
150 | headers := make(map[string]string)
151 | for _, key := range requestInfo.Headers.ListKeys() {
152 | value := requestInfo.Headers.Get(key)
153 | headers[key] = strings.Join(value, ",")
154 | }
155 | request.SetHeaders(headers)
156 |
157 | baseUri, err := getBaseUrl(br.adapter)
158 | if err != nil {
159 | return nil, err
160 | }
161 | var finalUrl = strings.Replace(uriString, baseUri.String(), "", 1)
162 | request.SetUrl(&finalUrl)
163 |
164 | return request, nil
165 | }
166 |
167 | // Send serializes and sends the batch request to the server
168 | func (br *batchRequest) Send(ctx context.Context, adapter abstractions.RequestAdapter) (BatchResponse, error) {
169 | baseUrl, err := getBaseUrl(adapter)
170 | if err != nil {
171 | return nil, err
172 | }
173 |
174 | requestInfo, err := buildRequestInfo(ctx, adapter, br, baseUrl)
175 | if err != nil {
176 | return nil, err
177 | }
178 | return sendBatchRequest(ctx, requestInfo, adapter)
179 | }
180 |
181 | func getBaseUrl(adapter abstractions.RequestAdapter) (*url.URL, error) {
182 | return url.Parse(adapter.GetBaseUrl())
183 | }
184 |
185 | func buildRequestInfo(ctx context.Context, adapter abstractions.RequestAdapter, body BatchRequest, baseUrl *url.URL) (*abstractions.RequestInformation, error) {
186 | requestInfo := abstractions.NewRequestInformation()
187 | requestInfo.Method = abstractions.POST
188 | requestInfo.UrlTemplate = "{+baseurl}/$batch"
189 | err := requestInfo.SetContentFromParsable(ctx, adapter, "application/json", body)
190 | if err != nil {
191 | return nil, err
192 | }
193 | requestInfo.Headers.Add("Content-Type", "application/json")
194 |
195 | return requestInfo, nil
196 | }
197 |
198 | func getResponsePrimaryContentType(responseItem BatchItem) string {
199 | header := responseItem.GetHeaders()
200 | if header == nil {
201 | return ""
202 | }
203 | rawType := header["Content-Type"]
204 | splat := strings.Split(rawType, ";")
205 | return strings.ToLower(splat[0])
206 | }
207 |
208 | func getRootParseNode(responseItem BatchItem) (absser.ParseNode, error) {
209 | contentType := getResponsePrimaryContentType(responseItem)
210 | if contentType == "" {
211 | return nil, nil
212 | }
213 |
214 | var (
215 | content []byte
216 | err error
217 | )
218 | if contentType == jsonContentType {
219 | if content, err = json.Marshal(responseItem.GetBody()); err != nil {
220 | return nil, err
221 | }
222 | } else {
223 | var buf bytes.Buffer
224 | if err = gob.NewEncoder(&buf).Encode(responseItem.GetBody()); err != nil {
225 | return nil, err
226 | }
227 | content = buf.Bytes()
228 | }
229 |
230 | return serialization.DefaultParseNodeFactoryInstance.GetRootParseNode(contentType, content)
231 | }
232 |
233 | func throwErrors(responseItem BatchItem, typeName string) error {
234 | errorMappings := getErrorMapper(typeName)
235 | if errorMappings == nil {
236 | errorMappings = getErrorMapper(BatchRequestErrorRegistryKey)
237 | }
238 | responseStatus := *responseItem.GetStatus()
239 |
240 | statusAsString := strconv.Itoa(int(responseStatus))
241 | var errorCtor absser.ParsableFactory = nil
242 | if len(errorMappings) != 0 {
243 | if responseStatus >= 400 && responseStatus < 500 && errorMappings["4XX"] != nil {
244 | errorCtor = errorMappings["4XX"]
245 | } else if responseStatus >= 500 && responseStatus < 600 && errorMappings["5XX"] != nil {
246 | errorCtor = errorMappings["5XX"]
247 | }
248 | }
249 |
250 | if errorCtor == nil {
251 | return &abstractions.ApiError{
252 | Message: "The server returned an unexpected status code and no error factory is registered for this code: " + statusAsString,
253 | }
254 | }
255 |
256 | rootNode, err := getRootParseNode(responseItem)
257 | if err != nil {
258 | return err
259 | }
260 | if rootNode == nil {
261 | return &abstractions.ApiError{
262 | Message: "The server returned an unexpected status code with no response body: " + statusAsString,
263 | }
264 | }
265 |
266 | errValue, err := rootNode.GetObjectValue(errorCtor)
267 | if err != nil {
268 | return err
269 | }
270 |
271 | return errValue.(error)
272 | }
273 |
274 | // GetBatchResponseById returns the response of the batch request item with the given id.
275 | func GetBatchResponseById[T serialization.Parsable](resp BatchResponse, itemId string, constructor absser.ParsableFactory) (T, error) {
276 | var res T
277 | item := resp.GetResponseById(itemId)
278 |
279 | if *item.GetStatus() >= 400 {
280 | return res, throwErrors(item, reflect.TypeOf(new(T)).Elem().Name())
281 | }
282 |
283 | jsonStr, err := json.Marshal(item.GetBody())
284 | if err != nil {
285 | return res, err
286 | }
287 |
288 | var parseNodeFactory = absser.DefaultParseNodeFactoryInstance
289 |
290 | parseNode, err := parseNodeFactory.GetRootParseNode(jsonContentType, jsonStr)
291 | if err != nil {
292 | return res, err
293 | }
294 |
295 | result, err := parseNode.GetObjectValue(constructor)
296 | return result.(T), err
297 | }
298 |
299 | func getErrorMapper(key string) abstractions.ErrorMappings {
300 | errorMapperSrc, found := GetErrorFactoryFromRegistry(key)
301 | if found {
302 | return errorMapperSrc
303 | }
304 | return nil
305 | }
306 |
307 | func sendBatchRequest(ctx context.Context, requestInfo *abstractions.RequestInformation, adapter abstractions.RequestAdapter) (BatchResponse, error) {
308 | if requestInfo == nil {
309 | return nil, errors.New("requestInfo cannot be nil")
310 | }
311 |
312 | response, err := adapter.Send(ctx, requestInfo, CreateBatchResponseDiscriminator, getErrorMapper(BatchRequestErrorRegistryKey))
313 | if err != nil {
314 | return nil, err
315 | }
316 |
317 | return response.(BatchResponse), nil
318 | }
319 |
--------------------------------------------------------------------------------
/batch_response_model.go:
--------------------------------------------------------------------------------
1 | package msgraphgocore
2 |
3 | import (
4 | "github.com/microsoft/kiota-abstractions-go/serialization"
5 | )
6 |
7 | type batchResponse struct {
8 | responses []BatchItem
9 | indexResponse map[string]BatchItem
10 | isIndexed bool
11 | }
12 |
13 | func NewBatchResponse() BatchResponse {
14 | return &batchResponse{
15 | indexResponse: make(map[string]BatchItem),
16 | isIndexed: false,
17 | }
18 | }
19 |
20 | // GetResponses returns a slice of BatchItem to the user
21 | func (br *batchResponse) GetResponses() []BatchItem {
22 | return br.responses
23 | }
24 |
25 | // SetResponses adds a slice of BatchItem to the response
26 | func (br *batchResponse) SetResponses(responses []BatchItem) {
27 | br.responses = responses
28 | }
29 |
30 | // AddResponses adds elements to existing response
31 | func (br *batchResponse) AddResponses(responses []BatchItem) {
32 | for _, v := range responses {
33 | br.responses = append(br.responses, v)
34 | }
35 | }
36 |
37 | // GetResponseById returns a response payload as a batch item
38 | func (br *batchResponse) GetResponseById(itemId string) BatchItem {
39 | if !br.isIndexed {
40 |
41 | for _, resp := range br.GetResponses() {
42 | br.indexResponse[*(resp.GetId())] = resp
43 | }
44 |
45 | br.isIndexed = true
46 | }
47 |
48 | return br.indexResponse[itemId]
49 | }
50 |
51 | // CreateBatchResponseDiscriminator creates a new instance of the appropriate class based on discriminator value
52 | func CreateBatchResponseDiscriminator(serialization.ParseNode) (serialization.Parsable, error) {
53 | return NewBatchResponse(), nil
54 | }
55 |
56 | // BatchResponse instance of batch request result payload
57 | type BatchResponse interface {
58 | serialization.Parsable
59 | GetResponses() []BatchItem
60 | SetResponses(responses []BatchItem)
61 | AddResponses(responses []BatchItem)
62 | GetResponseById(itemId string) BatchItem
63 | GetFailedResponses() map[string]int32
64 | GetStatusCodes() map[string]int32
65 | }
66 |
67 | // Serialize serializes information the current object
68 | func (br *batchResponse) Serialize(serialization.SerializationWriter) error {
69 | panic("batch responses are not serializable")
70 | }
71 |
72 | // GetFieldDeserializers the deserialization information for the current model
73 | func (br *batchResponse) GetFieldDeserializers() map[string]func(serialization.ParseNode) error {
74 | res := make(map[string]func(serialization.ParseNode) error)
75 | res["responses"] = func(n serialization.ParseNode) error {
76 | val, err := n.GetCollectionOfObjectValues(CreateBatchRequestItemDiscriminator)
77 | if err != nil {
78 | return err
79 | }
80 | if val != nil {
81 | res := make([]BatchItem, len(val))
82 | for i, v := range val {
83 | res[i] = v.(BatchItem)
84 | }
85 | br.SetResponses(res)
86 | }
87 | return nil
88 | }
89 | return res
90 | }
91 |
92 | // GetFailedResponses returns a map of responses that failed
93 | func (br *batchResponse) GetFailedResponses() map[string]int32 {
94 | statuses := make(map[string]int32)
95 | for _, response := range br.GetResponses() {
96 | if *response.GetStatus() > 399 && *response.GetStatus() < 600 {
97 | statuses[*response.GetId()] = *response.GetStatus()
98 | }
99 | }
100 | return statuses
101 | }
102 |
103 | // GetStatusCodes returns a map of responses statuses and the status codes
104 | func (br *batchResponse) GetStatusCodes() map[string]int32 {
105 | statuses := make(map[string]int32)
106 | for _, response := range br.GetResponses() {
107 | statuses[*response.GetId()] = *response.GetStatus()
108 | }
109 | return statuses
110 | }
111 |
--------------------------------------------------------------------------------
/docs/batch_request.md:
--------------------------------------------------------------------------------
1 | ## BatchRequest
2 |
3 | BatchRequest is useful when you want to make multiple requests efficiently. It batches all requests (upto 20 requests) into a json object and makes one api call. You can learn more about it on [Microsoft Docs](https://docs.microsoft.com/en-us/graph/json-batching).
4 |
5 | ## Code Sample
6 |
7 | ```go
8 | import "github.com/microsoftgraph/msgraph-sdk-go-core"
9 | import abstractions "github.com/microsoft/kiota-abstractions-go"
10 |
11 | reqInfo := client.Me().CreateGetRequestInformation()
12 | batch := msgraphsdkcore.NewBatchRequest()
13 | batchItem := batch.AppendBatchItem(*reqInfo)
14 |
15 | resp, err := batch.Send(reqAdapter)
16 |
17 | // print the first response
18 | user := GetBatchResponseById[User](resp, "1", CreateUserFromDiscriminatorValue) // returns a serialized response
19 | fmt.Println(user.GetDisplayName()) // Print display name
20 | ```
21 |
22 | ## Depends On Relationship
23 |
24 | BatchItem supports constructing a dependency chain for scenarios where you want one request to be sent out before another request is made. In the example below batchItem2 will be sent before batchItem1.
25 |
26 | ```go
27 | batchItem1 := batch.AppendBatchItem(*reqInfo)
28 | batchItem2 := batch.AppendBatchItem(*reqInfo)
29 |
30 | batchItem1.DependsOnItem(batchItem2)
31 | ```
32 |
33 | ## Adds BatchCollectionResponse
34 |
35 | `BatchRequestCollection` allows users to add more than 19 requests and send them as multiple `BatchRequest`'s. The send functionality of BatchRequestCollection splits the requests and sends them in serial.
36 |
37 | ```go
38 | batchCollection := msgraphgocore.NewBatchRequestCollection(client.GetAdapter())
39 |
40 | meRequestItem, _ := batchCollection.AddBatchRequestStep(*meRequest)
41 | eventsRequestItem, _ := batchCollection.AddBatchRequestStep(*eventsRequest)
42 |
43 | batchResponse, _ := batchCollection.Send(context.Background(), client.GetAdapter())
44 |
45 | // print the first response
46 | user := GetBatchResponseById[User](batchResponse, "1", CreateUserFromDiscriminatorValue) // returns a serialized response
47 | fmt.Println(user.GetDisplayName()) // Print display name
48 | ```
--------------------------------------------------------------------------------
/error_mappings_registry.go:
--------------------------------------------------------------------------------
1 | package msgraphgocore
2 |
3 | import (
4 | "errors"
5 | abstractions "github.com/microsoft/kiota-abstractions-go"
6 | "sync"
7 | )
8 |
9 | var lock = &sync.Mutex{}
10 |
11 | type errorRegistry struct {
12 | registry map[string]abstractions.ErrorMappings
13 | }
14 |
15 | var singleInstance *errorRegistry
16 |
17 | // Create a global thread safe singleton for global values
18 | func getInstance() *errorRegistry {
19 | if singleInstance == nil {
20 | lock.Lock()
21 | defer lock.Unlock()
22 | if singleInstance == nil {
23 | singleInstance = &errorRegistry{
24 | registry: make(map[string]abstractions.ErrorMappings),
25 | }
26 | }
27 | }
28 |
29 | return singleInstance
30 | }
31 |
32 | func RegisterError(key string, value abstractions.ErrorMappings) error {
33 | single := getInstance()
34 | _, found := single.registry[key]
35 | if !found {
36 | single.registry[key] = value
37 | return nil
38 | } else {
39 | return errors.New("object Factory already register")
40 | }
41 | }
42 |
43 | func DeRegisterError(key string) error {
44 | single := getInstance()
45 | _, found := single.registry[key]
46 | if found {
47 | delete(single.registry, key)
48 | return nil
49 | } else {
50 | return errors.New("object Factory does not exist register")
51 | }
52 | }
53 |
54 | func GetErrorFactoryFromRegistry(key string) (abstractions.ErrorMappings, bool) {
55 | single := getInstance()
56 | item, found := single.registry[key]
57 | return item, found
58 | }
59 |
--------------------------------------------------------------------------------
/error_mappings_registry_test.go:
--------------------------------------------------------------------------------
1 | package msgraphgocore
2 |
3 | import (
4 | abstractions "github.com/microsoft/kiota-abstractions-go"
5 | "github.com/stretchr/testify/assert"
6 | "github.com/stretchr/testify/require"
7 | "testing"
8 | )
9 |
10 | func TestRegistration(t *testing.T) {
11 | errorMapping := abstractions.ErrorMappings{}
12 | err := RegisterError(BatchRequestErrorRegistryKey, errorMapping)
13 | require.NoError(t, err)
14 | err = RegisterError(BatchRequestErrorRegistryKey, errorMapping)
15 | assert.Equal(t, err.Error(), "object Factory already register")
16 |
17 | err = DeRegisterError(BatchRequestErrorRegistryKey)
18 | require.NoError(t, err)
19 | err = DeRegisterError(BatchRequestErrorRegistryKey)
20 | assert.Equal(t, err.Error(), "object Factory does not exist register")
21 | }
22 |
--------------------------------------------------------------------------------
/fileuploader/file_uploader_util.go:
--------------------------------------------------------------------------------
1 | package fileuploader
2 |
3 | import (
4 | "strings"
5 | "time"
6 | )
7 |
8 | type rangePair struct {
9 | Start int64
10 | End int64
11 | }
12 |
13 | func stringIsNullOrEmpty(s string) bool {
14 | s = strings.TrimSpace(s)
15 | if s == "" || len(s) == 0 {
16 | return true
17 | }
18 | return false
19 | }
20 |
21 | type UploadSession interface {
22 | GetExpirationDateTime() *time.Time
23 | SetExpirationDateTime(expirationDateTime *time.Time)
24 | GetNextExpectedRanges() []string
25 | SetNextExpectedRanges(nextExpectedRanges []string)
26 | GetOdataType() *string
27 | GetUploadUrl() *string
28 | }
29 |
30 | type ProgressCallBack func(current int64, total int64)
31 |
32 | type UploadResult[T interface{}] interface {
33 | SetItemResponse(response T)
34 | GetItemResponse() T
35 | SetUploadSession(uploadSession UploadSession)
36 | GetUploadSession() UploadSession
37 | SetURI(uri *string)
38 | GetURI() *string
39 | SetUploadSucceeded(isSuccessful bool)
40 | GetUploadSucceeded() bool
41 | SetResponseErrors(errors []error)
42 | GetResponseErrors() []error
43 | }
44 |
45 | func NewUploadResult[T interface{}]() UploadResult[T] {
46 | return &uploadResult[T]{}
47 | }
48 |
49 | type uploadResult[T interface{}] struct {
50 | itemResponse T
51 | uploadSession UploadSession
52 | uri *string
53 | uploadSucceeded bool
54 | responseErrors []error
55 | }
56 |
57 | func (u *uploadResult[T]) SetItemResponse(response T) {
58 | u.itemResponse = response
59 | }
60 |
61 | func (u *uploadResult[T]) GetItemResponse() T {
62 | return u.itemResponse
63 | }
64 |
65 | func (u *uploadResult[T]) SetUploadSession(uploadSession UploadSession) {
66 | u.uploadSession = uploadSession
67 | }
68 |
69 | func (u *uploadResult[T]) GetUploadSession() UploadSession {
70 | return u.uploadSession
71 | }
72 |
73 | func (u *uploadResult[T]) SetURI(uri *string) {
74 | u.uri = uri
75 | }
76 |
77 | func (u *uploadResult[T]) GetURI() *string {
78 | return u.uri
79 | }
80 |
81 | func (u *uploadResult[T]) SetUploadSucceeded(isSuccessful bool) {
82 | u.uploadSucceeded = isSuccessful
83 | }
84 |
85 | func (u *uploadResult[T]) GetUploadSucceeded() bool {
86 | return u.uploadSucceeded
87 | }
88 |
89 | func (u *uploadResult[T]) SetResponseErrors(errors []error) {
90 | u.responseErrors = errors
91 | }
92 |
93 | func (u *uploadResult[T]) GetResponseErrors() []error {
94 | return u.responseErrors
95 | }
96 |
--------------------------------------------------------------------------------
/fileuploader/large_file_session.go:
--------------------------------------------------------------------------------
1 | package fileuploader
2 |
3 | import (
4 | "github.com/microsoft/kiota-abstractions-go/serialization"
5 | "time"
6 | )
7 |
8 | type UploadSessionResponse interface {
9 | serialization.Parsable
10 | GetExpirationDateTime() *time.Time
11 | SetExpirationDateTime(expirationDateTime *time.Time)
12 | GetNextExpectedRanges() []string
13 | SetNextExpectedRanges(nextExpectedRanges []string)
14 | }
15 |
16 | type largeFileUploadSession struct {
17 | expirationDateTime *time.Time
18 | nextExpectedRanges []string
19 | }
20 |
21 | func (l *largeFileUploadSession) Serialize(writer serialization.SerializationWriter) error {
22 | if l.expirationDateTime != nil {
23 | if err := writer.WriteTimeValue("expirationDateTime", l.expirationDateTime); err != nil {
24 | return err
25 | }
26 | }
27 | if l.nextExpectedRanges != nil {
28 | if err := writer.WriteCollectionOfStringValues("nextExpectedRanges", l.nextExpectedRanges); err != nil {
29 | return err
30 | }
31 | }
32 | return nil
33 | }
34 |
35 | func (l *largeFileUploadSession) GetFieldDeserializers() map[string]func(serialization.ParseNode) error {
36 | return map[string]func(serialization.ParseNode) error{
37 | "expirationDateTime": func(n serialization.ParseNode) error {
38 | val, err := n.GetTimeValue()
39 | if err != nil {
40 | return err
41 | }
42 | if val != nil {
43 | l.SetExpirationDateTime(val)
44 | }
45 | return nil
46 | },
47 | "nextExpectedRanges": func(n serialization.ParseNode) error {
48 | val, err := n.GetCollectionOfPrimitiveValues("string")
49 | if err != nil {
50 | return err
51 | }
52 | if val != nil {
53 | res := make([]string, len(val))
54 | for i, v := range val {
55 | if v != nil {
56 | res[i] = *(v.(*string))
57 | }
58 | }
59 | l.SetNextExpectedRanges(res)
60 | }
61 | return nil
62 | },
63 | }
64 | }
65 |
66 | func (l *largeFileUploadSession) GetExpirationDateTime() *time.Time {
67 | return l.expirationDateTime
68 | }
69 |
70 | func (l *largeFileUploadSession) SetExpirationDateTime(expirationDateTime *time.Time) {
71 | l.expirationDateTime = expirationDateTime
72 | }
73 |
74 | func (l *largeFileUploadSession) GetNextExpectedRanges() []string {
75 | return l.nextExpectedRanges
76 | }
77 |
78 | func (l *largeFileUploadSession) SetNextExpectedRanges(nextExpectedRanges []string) {
79 | l.nextExpectedRanges = nextExpectedRanges
80 | }
81 |
82 | func newLargeFileUploadSession() UploadSessionResponse {
83 | return &largeFileUploadSession{}
84 | }
85 |
86 | // CreateUploadSessionDiscriminator creates a new instance of the appropriate class based on discriminator value
87 | func CreateUploadSessionDiscriminator(serialization.ParseNode) (serialization.Parsable, error) {
88 | return newLargeFileUploadSession(), nil
89 | }
90 |
--------------------------------------------------------------------------------
/fileuploader/large_file_upload_task.go:
--------------------------------------------------------------------------------
1 | package fileuploader
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "io"
7 | "os"
8 | "strconv"
9 | "strings"
10 | "time"
11 |
12 | abstractions "github.com/microsoft/kiota-abstractions-go"
13 | "github.com/microsoft/kiota-abstractions-go/serialization"
14 | )
15 |
16 | type LargeFileUploadTask[T serialization.Parsable] interface {
17 | Upload(progress ProgressCallBack) UploadResult[T]
18 | Resume(progress ProgressCallBack) (UploadResult[T], error)
19 | RefreshUploadStatus() error
20 | Cancel() error
21 | }
22 |
23 | // ByteStream is an interface that represents a stream of bytes
24 | type ByteStream interface {
25 | io.ReaderAt
26 | Stat() (os.FileInfo, error)
27 | }
28 |
29 | type largeFileUploadTask[T serialization.Parsable] struct {
30 | uploadSession UploadSession
31 | adapter abstractions.RequestAdapter
32 | byteStream ByteStream // *os.File by default implements ByteStream
33 | maxSlice int64
34 | parsableFactory serialization.ParsableFactory
35 | errorMappings abstractions.ErrorMappings
36 | }
37 |
38 | func NewLargeFileUploadTask[T serialization.Parsable](adapter abstractions.RequestAdapter, uploadSession UploadSession, byteStream ByteStream, maxSlice int64, parsableFactory serialization.ParsableFactory, errorMappings abstractions.ErrorMappings) LargeFileUploadTask[T] {
39 | return &largeFileUploadTask[T]{
40 | adapter: adapter,
41 | uploadSession: uploadSession,
42 | byteStream: byteStream,
43 | maxSlice: maxSlice,
44 | parsableFactory: parsableFactory,
45 | errorMappings: errorMappings,
46 | }
47 | }
48 |
49 | // Upload uploads the byteStream in slices and returns the result of the upload
50 | func (l *largeFileUploadTask[T]) Upload(progress ProgressCallBack) UploadResult[T] {
51 | result := NewUploadResult[T]()
52 | slices := l.createUploadSlices()
53 | maxRetriesPerRequest := 3
54 |
55 | // slices of errors
56 | var responseErrors []error
57 | var itemResponse T
58 | var location *string
59 |
60 | for _, slice := range slices {
61 | response, uploadLocation, err := l.uploadWithRetry(slice, maxRetriesPerRequest)
62 | if err != nil {
63 | responseErrors = append(responseErrors, err)
64 | } else {
65 | progress(slice.RangeEnd, slice.TotalSessionLength)
66 | }
67 | if response != nil {
68 | itemResponse = response.(T)
69 | }
70 | location = uploadLocation
71 | }
72 |
73 | if len(responseErrors) > 0 {
74 | result.SetUploadSucceeded(false)
75 | result.SetResponseErrors(responseErrors)
76 | } else {
77 | result.SetUploadSucceeded(true)
78 | result.SetUploadSession(l.uploadSession)
79 | result.SetItemResponse(itemResponse)
80 | result.SetURI(location)
81 | }
82 |
83 | return result
84 | }
85 |
86 | // Resume uploads the byteStream in slices and returns the result of the upload
87 | func (l *largeFileUploadTask[T]) Resume(progress ProgressCallBack) (UploadResult[T], error) {
88 | err := l.RefreshUploadStatus()
89 | if err != nil {
90 | return nil, err
91 | }
92 |
93 | if len(l.uploadSession.GetNextExpectedRanges()) == 0 {
94 | return nil, errors.New("UploadSession does not have next expected ranges")
95 | }
96 |
97 | if l.uploadSession.GetExpirationDateTime().Before(time.Now()) {
98 | return nil, errors.New("UploadSession has expired")
99 | }
100 |
101 | return l.Upload(progress), nil
102 | }
103 |
104 | func (l *largeFileUploadTask[T]) RefreshUploadStatus() error {
105 | requestInfo := abstractions.NewRequestInformation()
106 | requestInfo.UrlTemplate = *l.uploadSession.GetUploadUrl()
107 | requestInfo.Method = abstractions.GET
108 | requestInfo.Headers.TryAdd("Accept", "application/json")
109 |
110 | result, err := l.adapter.Send(context.Background(), requestInfo, CreateUploadSessionDiscriminator, l.errorMappings)
111 | if err != nil {
112 | return err
113 | }
114 |
115 | sessionResponse := result.(UploadSessionResponse)
116 |
117 | l.uploadSession.SetExpirationDateTime(sessionResponse.GetExpirationDateTime())
118 | l.uploadSession.SetNextExpectedRanges(sessionResponse.GetNextExpectedRanges())
119 |
120 | return nil
121 | }
122 |
123 | // Cancel cancels the upload
124 | func (l *largeFileUploadTask[T]) Cancel() error {
125 | requestInfo := abstractions.NewRequestInformationWithMethodAndUrlTemplateAndPathParameters(abstractions.DELETE, *l.uploadSession.GetUploadUrl(), make(map[string]string))
126 | err := l.adapter.SendNoContent(context.Background(), requestInfo, l.errorMappings)
127 | return err
128 | }
129 |
130 | func (l *largeFileUploadTask[T]) uploadWithRetry(slice uploadSlice[T], maxRetry int) (interface{}, *string, error) {
131 | retry := 1
132 | var parseable interface{}
133 | var location *string
134 | var err error
135 | for retry < maxRetry {
136 | // store the result of the upload
137 | parseable, location, err = slice.Upload(l.parsableFactory) // check if successful
138 | if err != nil {
139 | if retry >= maxRetry {
140 | return nil, nil, err
141 | }
142 | // backoff before retrying
143 | time.Sleep(time.Duration(retry) * time.Second)
144 | } else {
145 | return parseable, location, err // return the result as the upload was successful
146 | }
147 | retry++
148 | }
149 | return parseable, location, err
150 | }
151 |
152 | func (l *largeFileUploadTask[T]) getRangesRemaining() []rangePair {
153 | rangePairs := make([]rangePair, len(l.uploadSession.GetNextExpectedRanges()))
154 |
155 | for i, ranges := range l.uploadSession.GetNextExpectedRanges() {
156 | rangeValues := strings.Split(ranges, "-")
157 |
158 | var startRange int64
159 | if s, err := strconv.ParseInt(rangeValues[0], 10, 64); err == nil {
160 | startRange = s
161 | }
162 |
163 | var endRange int64
164 | if !stringIsNullOrEmpty(rangeValues[1]) {
165 | if s, err := strconv.ParseInt(rangeValues[1], 10, 64); err == nil {
166 | if endRange > l.fileSize() {
167 | endRange = l.fileSize() - 1
168 | } else {
169 | endRange = s
170 | }
171 | }
172 | } else {
173 | endRange = l.fileSize() - 1
174 | }
175 |
176 | rangePairs[i] = rangePair{
177 | Start: startRange,
178 | End: endRange,
179 | }
180 | }
181 |
182 | return rangePairs
183 | }
184 |
185 | // returns the size of a byteStream
186 | func (l *largeFileUploadTask[T]) fileSize() int64 {
187 | fileInfo, _ := l.byteStream.Stat()
188 | return fileInfo.Size()
189 | }
190 |
--------------------------------------------------------------------------------
/fileuploader/large_file_upload_test.go:
--------------------------------------------------------------------------------
1 | package fileuploader
2 |
3 | import (
4 | "fmt"
5 | abstractions "github.com/microsoft/kiota-abstractions-go"
6 | "github.com/microsoft/kiota-abstractions-go/authentication"
7 | absser "github.com/microsoft/kiota-abstractions-go/serialization"
8 | jsonserialization "github.com/microsoft/kiota-serialization-json-go"
9 | msgraphgocore "github.com/microsoftgraph/msgraph-sdk-go-core"
10 | "github.com/microsoftgraph/msgraph-sdk-go-core/internal"
11 | "github.com/stretchr/testify/assert"
12 | "net/http"
13 | "net/http/httptest"
14 | "testing"
15 | "time"
16 | )
17 |
18 | func prepareUploader(testServer *httptest.Server) LargeFileUploadTask[internal.UploadResponseble] {
19 | absser.DefaultParseNodeFactoryInstance.ContentTypeAssociatedFactories["application/json"] = jsonserialization.NewJsonParseNodeFactory()
20 |
21 | reqAdapter, _ := msgraphgocore.NewGraphRequestAdapterBase(&authentication.AnonymousAuthenticationProvider{}, msgraphgocore.GraphClientOptions{
22 | GraphServiceVersion: "",
23 | GraphServiceLibraryVersion: "",
24 | })
25 |
26 | mockPath := testServer.URL + "/uploadUrl"
27 | reqAdapter.SetBaseUrl(mockPath)
28 |
29 | byteStream := &internal.MockByteStream{
30 | Content: []byte("mock byteStream content"),
31 | }
32 |
33 | uploadSession := &mockUploadSession{
34 | UploadUrl: mockPath,
35 | ExpectedRanges: []string{"0-4", "6-"},
36 | OdataType: "odatatype",
37 | ExpirationDateTime: time.Time{},
38 | }
39 | maxSliceSize := 2
40 |
41 | errorMapping := abstractions.ErrorMappings{
42 | "4XX": internal.CreateSampleErrorFromDiscriminatorValue,
43 | "5XX": internal.CreateSampleErrorFromDiscriminatorValue,
44 | }
45 |
46 | return NewLargeFileUploadTask[internal.UploadResponseble](reqAdapter, uploadSession, byteStream, int64(maxSliceSize), internal.CreateUploadResponseFromDiscriminatorValue, errorMapping)
47 | }
48 |
49 | func TestLargeFileUploadTask(t *testing.T) {
50 | testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
51 | w.Header().Set("Content-Type", "application/json")
52 | jsonResponse := `{
53 | "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#microsoft.graph.uploadSession",
54 | "uploadUrl": "https://uploadUrl",
55 | "expirationDateTime": "2021-08-10T00:00:00Z"
56 | }`
57 | w.WriteHeader(200)
58 | fmt.Fprint(w, jsonResponse)
59 | }))
60 | defer testServer.Close()
61 |
62 | uploader := prepareUploader(testServer)
63 |
64 | // verify that the object was created correctly
65 | // verify the number of sub upload tasks
66 | progressCall := 0
67 | var previousValue int64 = 0
68 | progress := func(progress int64, total int64) {
69 | progressCall++
70 | if previousValue > progress {
71 | assert.Fail(t, "progress should not decrease")
72 | }
73 | previousValue = progress
74 | }
75 | result := uploader.Upload(progress)
76 |
77 | // verify that status is correct
78 | assert.True(t, result.GetUploadSucceeded())
79 | assert.Equal(t, 12, progressCall) // progress callback should be called for every sub upload task
80 | }
81 |
82 | func TestResumeLargeFileUploadTask(t *testing.T) {
83 | testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
84 | w.Header().Set("Content-Type", "application/json")
85 |
86 | testTime := time.Now().Add(1 * time.Hour).Format("2006-01-02T15:04:05Z")
87 | jsonResponse := `{
88 | "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#microsoft.graph.uploadSession",
89 | "uploadUrl": "https://uploadUrl",
90 | "expirationDateTime": "%s",
91 | "nextExpectedRanges": ["0-4", "6-"]
92 | }`
93 | w.WriteHeader(200)
94 | formattedResponse := fmt.Sprintf(jsonResponse, testTime)
95 | fmt.Fprint(w, formattedResponse)
96 | }))
97 | defer testServer.Close()
98 |
99 | uploader := prepareUploader(testServer)
100 |
101 | progressCall := 0
102 | progress := func(progress int64, total int64) {
103 | progressCall++
104 | }
105 | result, err := uploader.Resume(progress)
106 | assert.NoError(t, err)
107 |
108 | // verify that status is correct
109 | assert.True(t, result.GetUploadSucceeded())
110 | assert.Equal(t, 12, progressCall) // progress callback should be called for every sub upload task
111 |
112 | }
113 |
114 | func TestCancelLargeFileUploadTask(t *testing.T) {
115 |
116 | var receivedReq *http.Request
117 | testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
118 | w.Header().Set("Content-Type", "application/json")
119 | w.WriteHeader(204)
120 | receivedReq = req
121 | }))
122 | defer testServer.Close()
123 |
124 | uploader := prepareUploader(testServer)
125 | err := uploader.Cancel()
126 | assert.NoError(t, err)
127 | assert.Equal(t, "DELETE", receivedReq.Method)
128 | }
129 |
130 | type mockUploadSession struct {
131 | UploadUrl string
132 | ExpectedRanges []string
133 | OdataType string
134 | ExpirationDateTime time.Time
135 | }
136 |
137 | func (m *mockUploadSession) SetExpirationDateTime(expirationDateTime *time.Time) {
138 | m.ExpirationDateTime = *expirationDateTime
139 | }
140 |
141 | func (m *mockUploadSession) SetNextExpectedRanges(nextExpectedRanges []string) {
142 | m.ExpectedRanges = nextExpectedRanges
143 | }
144 |
145 | func (m *mockUploadSession) Serialize(writer absser.SerializationWriter) error {
146 | return nil
147 | }
148 |
149 | func (m *mockUploadSession) GetFieldDeserializers() map[string]func(absser.ParseNode) error {
150 | return make(map[string]func(absser.ParseNode) error)
151 | }
152 |
153 | func (m *mockUploadSession) GetExpirationDateTime() *time.Time {
154 | return &m.ExpirationDateTime
155 | }
156 |
157 | func (m *mockUploadSession) GetNextExpectedRanges() []string {
158 | return m.ExpectedRanges
159 | }
160 |
161 | func (m *mockUploadSession) GetOdataType() *string {
162 | return &m.OdataType
163 | }
164 |
165 | func (m *mockUploadSession) GetUploadUrl() *string {
166 | return &m.UploadUrl
167 | }
168 |
--------------------------------------------------------------------------------
/fileuploader/upload_slice.go:
--------------------------------------------------------------------------------
1 | package fileuploader
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | abstractions "github.com/microsoft/kiota-abstractions-go"
7 | "github.com/microsoft/kiota-abstractions-go/serialization"
8 | nethttplibrary "github.com/microsoft/kiota-http-go"
9 | "time"
10 | )
11 |
12 | const binaryContentType = "application/octet-stream"
13 |
14 | const uriLocationHeader = "Location"
15 |
16 | type uploadSlice[T serialization.Parsable] struct {
17 | RequestAdapter abstractions.RequestAdapter
18 | UrlTemplate string
19 | RangeBegin int64
20 | RangeEnd int64
21 | TotalSessionLength int64
22 | RangeLength int64
23 | byteStream ByteStream
24 | errorMappings abstractions.ErrorMappings
25 | }
26 |
27 | func (l *largeFileUploadTask[T]) createUploadSlices() []uploadSlice[T] {
28 |
29 | requestRanges := l.getRangesRemaining()
30 | maxSlice := l.maxSlice
31 | totalSessionLength := l.fileSize()
32 |
33 | // compute the correct upload ranges by splitting the values of ranges remaining from start to end
34 | var uploadSlices []uploadSlice[T]
35 | for _, v := range requestRanges {
36 | start := v.Start
37 | for start < totalSessionLength && start <= v.End {
38 | end := minOf(v.End, (start+maxSlice)-1, totalSessionLength-1)
39 | uploadSlices = append(uploadSlices, uploadSlice[T]{
40 | RequestAdapter: l.adapter,
41 | UrlTemplate: *l.uploadSession.GetUploadUrl(),
42 | RangeBegin: start,
43 | RangeEnd: end,
44 | RangeLength: end - start + 1,
45 | TotalSessionLength: totalSessionLength,
46 | errorMappings: l.errorMappings,
47 | byteStream: l.byteStream,
48 | })
49 | start = end + 1
50 | }
51 | }
52 |
53 | return uploadSlices
54 | }
55 |
56 | func minOf(vars ...int64) int64 {
57 | minimum := vars[0]
58 | for _, i := range vars {
59 | if minimum > i {
60 | minimum = i
61 | }
62 | }
63 | return minimum
64 | }
65 |
66 | func (u *uploadSlice[T]) Upload(parsableFactory serialization.ParsableFactory) (interface{}, *string, error) {
67 | data, err := u.readSection(u.RangeBegin, u.RangeEnd)
68 | if err != nil {
69 | return nil, nil, err
70 | }
71 | requestInfo := u.createRequestInformation(data)
72 |
73 | // limit the upload time per slice to 5 minutes
74 | ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
75 | defer cancel()
76 |
77 | headerOptions := nethttplibrary.NewHeadersInspectionOptions()
78 | headerOptions.InspectResponseHeaders = true
79 | requestInfo.AddRequestOptions([]abstractions.RequestOption{headerOptions})
80 |
81 | result, err := u.RequestAdapter.Send(ctx, requestInfo, parsableFactory, u.errorMappings)
82 |
83 | var location *string = nil
84 | locations := headerOptions.GetResponseHeaders().Get(uriLocationHeader)
85 | if len(locations) > 0 {
86 | location = &locations[0]
87 | }
88 |
89 | return result, location, err
90 | }
91 |
92 | func (u *uploadSlice[T]) readSection(start, end int64) ([]byte, error) {
93 | length := (end - start) + 1
94 |
95 | buffer := make([]byte, length)
96 | _, err := u.byteStream.ReadAt(buffer, start)
97 | if err != nil {
98 | return nil, err
99 | }
100 |
101 | return buffer, nil
102 | }
103 |
104 | func (u *uploadSlice[T]) createRequestInformation(content []byte) *abstractions.RequestInformation {
105 | headers := abstractions.NewRequestHeaders()
106 | headers.Add("Content-Range", fmt.Sprintf("bytes %d-%d/%d", u.RangeBegin, u.RangeEnd, u.TotalSessionLength))
107 | headers.Add("Content-Length", fmt.Sprintf("%d", u.RangeLength))
108 |
109 | requestInfo := abstractions.NewRequestInformation()
110 | requestInfo.Headers = headers
111 | requestInfo.UrlTemplate = u.UrlTemplate
112 | requestInfo.Method = abstractions.PUT
113 | requestInfo.SetStreamContentAndContentType(content, binaryContentType)
114 | return requestInfo
115 | }
116 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/microsoftgraph/msgraph-sdk-go-core
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.24.1
6 |
7 | require (
8 | github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0
9 | github.com/google/uuid v1.6.0
10 | github.com/microsoft/kiota-abstractions-go v1.9.2
11 | github.com/microsoft/kiota-authentication-azure-go v1.3.0
12 | github.com/microsoft/kiota-http-go v1.5.4
13 | github.com/microsoft/kiota-serialization-json-go v1.1.2
14 | github.com/stretchr/testify v1.10.0
15 | )
16 |
17 | require (
18 | github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.0 // indirect
19 | github.com/davecgh/go-spew v1.1.1 // indirect
20 | github.com/go-logr/logr v1.4.2 // indirect
21 | github.com/go-logr/stdr v1.2.2 // indirect
22 | github.com/pmezard/go-difflib v1.0.0 // indirect
23 | github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3 // indirect
24 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect
25 | go.opentelemetry.io/otel v1.35.0 // indirect
26 | go.opentelemetry.io/otel/metric v1.35.0 // indirect
27 | go.opentelemetry.io/otel/trace v1.35.0 // indirect
28 | golang.org/x/net v0.38.0 // indirect
29 | golang.org/x/text v0.23.0 // indirect
30 | gopkg.in/yaml.v3 v3.0.1 // indirect
31 | )
32 |
33 | retract (
34 | v0.11.0
35 | // error in version bump, bumped minor instead of patch, causing issues with update commands as long as we don't have a higher version number
36 | v0.0.14
37 | // contains retraction only
38 | )
39 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
2 | github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
3 | github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.0 h1:Bg8m3nq/X1DeePkAbCfb6ml6F3F0IunEhE8TMh+lY48=
4 | github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.0/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA=
5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
8 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
9 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
10 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
11 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
12 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
13 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
14 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
15 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
16 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
17 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
18 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
19 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
20 | github.com/microsoft/kiota-abstractions-go v1.9.2 h1:3U5VgN2YGe3lsu1pyuS0t5jxv1llxX2ophwX8ewE6wQ=
21 | github.com/microsoft/kiota-abstractions-go v1.9.2/go.mod h1:f06pl3qSyvUHEfVNkiRpXPkafx7khZqQEb71hN/pmuU=
22 | github.com/microsoft/kiota-authentication-azure-go v1.3.0 h1:PWH6PgtzhJjnmvR6N1CFjriwX09Kv7S5K3vL6VbPVrg=
23 | github.com/microsoft/kiota-authentication-azure-go v1.3.0/go.mod h1:l/MPGUVvD7xfQ+MYSdZaFPv0CsLDqgSOp8mXwVgArIs=
24 | github.com/microsoft/kiota-http-go v1.5.4 h1:wSUmL1J+bTQlAWHjbRkSwr+SPAkMVYeYxxB85Zw0KFs=
25 | github.com/microsoft/kiota-http-go v1.5.4/go.mod h1:L+5Ri+SzwELnUcNA0cpbFKp/pBbvypLh3Cd1PR6sjx0=
26 | github.com/microsoft/kiota-serialization-json-go v1.1.2 h1:eJrPWeQ665nbjO0gsHWJ0Bw6V/ZHHU1OfFPaYfRG39k=
27 | github.com/microsoft/kiota-serialization-json-go v1.1.2/go.mod h1:deaGt7fjZarywyp7TOTiRsjfYiyWxwJJPQZytXwYQn8=
28 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
29 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
30 | github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
31 | github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
32 | github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3 h1:7hth9376EoQEd1hH4lAp3vnaLP2UMyxuMMghLKzDHyU=
33 | github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3/go.mod h1:Z5KcoM0YLC7INlNhEezeIZ0TZNYf7WSNO0Lvah4DSeQ=
34 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
35 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
36 | go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
37 | go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
38 | go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
39 | go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
40 | go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
41 | go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
42 | go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
43 | go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
44 | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
45 | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
46 | golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
47 | golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
48 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
49 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
50 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
51 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
52 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
53 |
--------------------------------------------------------------------------------
/graph_client_factory.go:
--------------------------------------------------------------------------------
1 | package msgraphgocore
2 |
3 | import (
4 | nethttp "net/http"
5 |
6 | khttp "github.com/microsoft/kiota-http-go"
7 | )
8 |
9 | var ReplacementPairs = map[string]string{"/users/me-token-to-replace": "/me"}
10 |
11 | // GetDefaultMiddlewaresWithOptions creates a default slice of middleware for the Graph Client.
12 | func GetDefaultMiddlewaresWithOptions(options *GraphClientOptions) []khttp.Middleware {
13 | kiotaMiddlewares := khttp.GetDefaultMiddlewares()
14 | graphMiddlewares := []khttp.Middleware{
15 | NewGraphTelemetryHandler(options),
16 | khttp.NewUrlReplaceHandler(true, ReplacementPairs),
17 | }
18 | graphMiddlewaresLen := len(graphMiddlewares)
19 | resultMiddlewares := make([]khttp.Middleware, len(kiotaMiddlewares)+graphMiddlewaresLen)
20 | copy(resultMiddlewares, graphMiddlewares)
21 | copy(resultMiddlewares[graphMiddlewaresLen:], kiotaMiddlewares)
22 | return resultMiddlewares
23 | }
24 |
25 | // GetDefaultClient creates a new http client with a preconfigured middleware pipeline
26 | func GetDefaultClient(options *GraphClientOptions, middleware ...khttp.Middleware) *nethttp.Client {
27 | if len(middleware) == 0 {
28 | middleware = GetDefaultMiddlewaresWithOptions(options)
29 | }
30 | return khttp.GetDefaultClient(middleware...)
31 | }
32 |
--------------------------------------------------------------------------------
/graph_client_options.go:
--------------------------------------------------------------------------------
1 | package msgraphgocore
2 |
3 | // GraphClientOptions represents a combination of GraphServiceVersion and GraphServiceLibraryVersion
4 | //
5 | // GraphServiceVersion is version of the targeted service.
6 | // GraphServiceLibraryVersion is the version of the service library
7 | type GraphClientOptions struct {
8 | GraphServiceVersion string
9 | GraphServiceLibraryVersion string
10 | }
11 |
--------------------------------------------------------------------------------
/graph_request_adapter_base.go:
--------------------------------------------------------------------------------
1 | package msgraphgocore
2 |
3 | import (
4 | "errors"
5 | nethttp "net/http"
6 |
7 | absauth "github.com/microsoft/kiota-abstractions-go/authentication"
8 | absser "github.com/microsoft/kiota-abstractions-go/serialization"
9 | khttp "github.com/microsoft/kiota-http-go"
10 | )
11 |
12 | // GraphRequestAdapterBase is the core service used by GraphServiceClient to make requests to Microsoft Graph.
13 | type GraphRequestAdapterBase struct {
14 | khttp.NetHttpRequestAdapter
15 | }
16 |
17 | // NewGraphRequestAdapterBase creates a new GraphRequestAdapterBase with the given parameters
18 | func NewGraphRequestAdapterBase(authenticationProvider absauth.AuthenticationProvider, clientOptions GraphClientOptions) (*GraphRequestAdapterBase, error) {
19 | return NewGraphRequestAdapterBaseWithParseNodeFactory(authenticationProvider, clientOptions, nil)
20 | }
21 |
22 | // NewGraphRequestAdapterBaseWithParseNodeFactory creates a new GraphRequestAdapterBase with the given parameters
23 | func NewGraphRequestAdapterBaseWithParseNodeFactory(authenticationProvider absauth.AuthenticationProvider, clientOptions GraphClientOptions, parseNodeFactory absser.ParseNodeFactory) (*GraphRequestAdapterBase, error) {
24 | return NewGraphRequestAdapterBaseWithParseNodeFactoryAndSerializationWriterFactory(authenticationProvider, clientOptions, parseNodeFactory, nil)
25 | }
26 |
27 | // NewGraphRequestAdapterBaseWithParseNodeFactoryAndSerializationWriterFactory creates a new GraphRequestAdapterBase with the given parameters
28 | func NewGraphRequestAdapterBaseWithParseNodeFactoryAndSerializationWriterFactory(authenticationProvider absauth.AuthenticationProvider, clientOptions GraphClientOptions, parseNodeFactory absser.ParseNodeFactory, serializationWriterFactory absser.SerializationWriterFactory) (*GraphRequestAdapterBase, error) {
29 | return NewGraphRequestAdapterBaseWithParseNodeFactoryAndSerializationWriterFactoryAndHttpClient(authenticationProvider, clientOptions, parseNodeFactory, serializationWriterFactory, nil)
30 | }
31 |
32 | // NewGraphRequestAdapterBaseWithParseNodeFactoryAndSerializationWriterFactoryAndHttpClient creates a new GraphRequestAdapterBase with the given parameters
33 | func NewGraphRequestAdapterBaseWithParseNodeFactoryAndSerializationWriterFactoryAndHttpClient(authenticationProvider absauth.AuthenticationProvider, clientOptions GraphClientOptions, parseNodeFactory absser.ParseNodeFactory, serializationWriterFactory absser.SerializationWriterFactory, httpClient *nethttp.Client) (*GraphRequestAdapterBase, error) {
34 | if authenticationProvider == nil {
35 | return nil, errors.New("authenticationProvider cannot be nil")
36 | }
37 | if httpClient == nil {
38 | httpClient = GetDefaultClient(&clientOptions)
39 | }
40 | if serializationWriterFactory == nil {
41 | serializationWriterFactory = absser.DefaultSerializationWriterFactoryInstance
42 | }
43 | if parseNodeFactory == nil {
44 | parseNodeFactory = absser.DefaultParseNodeFactoryInstance
45 | }
46 | baseAdapter, err := khttp.NewNetHttpRequestAdapterWithParseNodeFactoryAndSerializationWriterFactoryAndHttpClient(authenticationProvider, parseNodeFactory, serializationWriterFactory, httpClient)
47 | if err != nil {
48 | return nil, err
49 | }
50 | result := &GraphRequestAdapterBase{
51 | NetHttpRequestAdapter: *baseAdapter,
52 | }
53 |
54 | return result, nil
55 | }
56 |
--------------------------------------------------------------------------------
/graph_telemetry_handler.go:
--------------------------------------------------------------------------------
1 | package msgraphgocore
2 |
3 | import (
4 | nethttp "net/http"
5 |
6 | runtime "runtime"
7 |
8 | uuid "github.com/google/uuid"
9 | khttp "github.com/microsoft/kiota-http-go"
10 | )
11 |
12 | // GraphTelemetryHandler is a middleware handler that adds telemetry headers to requests.
13 | type GraphTelemetryHandler struct {
14 | sdkVersion string
15 | }
16 |
17 | // NewGraphTelemetryHandler creates a new GraphTelemetryHandler.
18 | func NewGraphTelemetryHandler(options *GraphClientOptions) *GraphTelemetryHandler {
19 | serviceVersionPrefix := ""
20 | if options != nil && options.GraphServiceLibraryVersion != "" {
21 | serviceVersionPrefix += "graph-go"
22 | if options.GraphServiceVersion != "" {
23 | serviceVersionPrefix += "-" + options.GraphServiceVersion
24 | }
25 | serviceVersionPrefix += "/" + options.GraphServiceLibraryVersion
26 | serviceVersionPrefix += ", "
27 | }
28 | featuresSuffix := ""
29 | if runtime.GOOS != "" {
30 | featuresSuffix += " hostOS=" + runtime.GOOS + ";"
31 | }
32 | if runtime.GOARCH != "" {
33 | featuresSuffix += " hostArch=" + runtime.GOARCH + ";"
34 | }
35 | goVersion := runtime.Version()
36 | if goVersion != "" {
37 | featuresSuffix += " runtimeEnvironment=" + goVersion + ";"
38 | }
39 | if featuresSuffix != "" {
40 | featuresSuffix = " (" + featuresSuffix[1:] + ")"
41 | }
42 | return &GraphTelemetryHandler{
43 | sdkVersion: serviceVersionPrefix + "graph-go-core/" + CoreVersion + featuresSuffix,
44 | }
45 | }
46 | func (middleware GraphTelemetryHandler) Intercept(pipeline khttp.Pipeline, middlewareIndex int, req *nethttp.Request) (*nethttp.Response, error) {
47 | req.Header.Add("SdkVersion", middleware.sdkVersion)
48 | req.Header.Add("client-request-id", uuid.NewString())
49 | return pipeline.Next(req, middlewareIndex)
50 | }
51 |
--------------------------------------------------------------------------------
/graph_telemetry_handler_test.go:
--------------------------------------------------------------------------------
1 | package msgraphgocore
2 |
3 | import (
4 | nethttp "net/http"
5 | httptest "net/http/httptest"
6 | testing "testing"
7 |
8 | assert "github.com/stretchr/testify/assert"
9 | )
10 |
11 | type NoopPipeline struct {
12 | client *nethttp.Client
13 | }
14 |
15 | func (pipeline *NoopPipeline) Next(req *nethttp.Request, middlewareIndex int) (*nethttp.Response, error) {
16 | return pipeline.client.Do(req)
17 | }
18 | func newNoopPipeline() *NoopPipeline {
19 | return &NoopPipeline{
20 | client: nethttp.DefaultClient,
21 | }
22 | }
23 |
24 | func TestItCreatesANewHandler(t *testing.T) {
25 | handler := NewGraphTelemetryHandler(&GraphClientOptions{})
26 | if handler == nil {
27 | t.Error("handler is nil")
28 | }
29 | }
30 |
31 | func TestItAddsHeaders(t *testing.T) {
32 | testServer := httptest.NewServer(nethttp.HandlerFunc(func(res nethttp.ResponseWriter, req *nethttp.Request) {
33 | res.WriteHeader(200)
34 | res.Write([]byte("body"))
35 | }))
36 | defer func() { testServer.Close() }()
37 | handler := NewGraphTelemetryHandler(&GraphClientOptions{})
38 | req, err := nethttp.NewRequest(nethttp.MethodGet, testServer.URL, nil)
39 | if err != nil {
40 | t.Error(err)
41 | }
42 | resp, err := handler.Intercept(newNoopPipeline(), 0, req)
43 | if err != nil {
44 | t.Error(err)
45 | }
46 | assert.NotNil(t, resp)
47 | sdkVersionHeaderValue := req.Header[nethttp.CanonicalHeaderKey("SdkVersion")]
48 | assert.NotEmpty(t, sdkVersionHeaderValue)
49 | assert.Contains(t, sdkVersionHeaderValue[0], "graph-go-core")
50 | assert.Contains(t, sdkVersionHeaderValue[0], "hostOS")
51 | assert.Contains(t, sdkVersionHeaderValue[0], "hostArch")
52 | assert.Contains(t, sdkVersionHeaderValue[0], "runtimeEnvironment")
53 | assert.NotEmpty(t, req.Header[nethttp.CanonicalHeaderKey("client-request-id")])
54 | }
55 |
56 | func TestItAddsServiceLibInfo(t *testing.T) {
57 | testServer := httptest.NewServer(nethttp.HandlerFunc(func(res nethttp.ResponseWriter, req *nethttp.Request) {
58 | res.WriteHeader(200)
59 | res.Write([]byte("body"))
60 | }))
61 | defer func() { testServer.Close() }()
62 | handler := NewGraphTelemetryHandler(&GraphClientOptions{
63 | GraphServiceLibraryVersion: "1.0.0",
64 | })
65 | req, err := nethttp.NewRequest(nethttp.MethodGet, testServer.URL, nil)
66 | if err != nil {
67 | t.Error(err)
68 | }
69 | resp, err := handler.Intercept(newNoopPipeline(), 0, req)
70 | if err != nil {
71 | t.Error(err)
72 | }
73 | assert.NotNil(t, resp)
74 | sdkVersionHeaderValue := req.Header[nethttp.CanonicalHeaderKey("SdkVersion")]
75 | assert.NotEmpty(t, sdkVersionHeaderValue)
76 | assert.Contains(t, sdkVersionHeaderValue[0], "graph-go/")
77 | }
78 |
79 | func TestItAddsServiceInfo(t *testing.T) {
80 | testServer := httptest.NewServer(nethttp.HandlerFunc(func(res nethttp.ResponseWriter, req *nethttp.Request) {
81 | res.WriteHeader(200)
82 | res.Write([]byte("body"))
83 | }))
84 | defer func() { testServer.Close() }()
85 | handler := NewGraphTelemetryHandler(&GraphClientOptions{
86 | GraphServiceLibraryVersion: "1.0.0",
87 | GraphServiceVersion: "v1",
88 | })
89 | req, err := nethttp.NewRequest(nethttp.MethodGet, testServer.URL, nil)
90 | if err != nil {
91 | t.Error(err)
92 | }
93 | resp, err := handler.Intercept(newNoopPipeline(), 0, req)
94 | if err != nil {
95 | t.Error(err)
96 | }
97 | assert.NotNil(t, resp)
98 | sdkVersionHeaderValue := req.Header[nethttp.CanonicalHeaderKey("SdkVersion")]
99 | assert.NotEmpty(t, sdkVersionHeaderValue)
100 | assert.Contains(t, sdkVersionHeaderValue[0], "graph-go-v1/")
101 | }
102 |
--------------------------------------------------------------------------------
/internal/errors.go:
--------------------------------------------------------------------------------
1 | package internal
2 |
3 | import (
4 | abstractions "github.com/microsoft/kiota-abstractions-go"
5 | "github.com/microsoft/kiota-abstractions-go/serialization"
6 | )
7 |
8 | type SampleError struct {
9 | abstractions.ApiError
10 | // Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.
11 | additionalData map[string]interface{}
12 | }
13 |
14 | func (s SampleError) Serialize(writer serialization.SerializationWriter) error {
15 | return nil
16 | }
17 |
18 | func (s *SampleError) GetFieldDeserializers() map[string]func(serialization.ParseNode) error {
19 | res := make(map[string]func(serialization.ParseNode) error)
20 | res["error"] = func(n serialization.ParseNode) error {
21 | v, err := n.GetRawValue()
22 | if err != nil {
23 | return err
24 | }
25 | if vm, ok := v.(map[string]interface{}); ok {
26 | if msg, ok := vm["message"]; ok && msg != nil {
27 | s.Message = *msg.(*string)
28 | }
29 | }
30 | return nil
31 | }
32 | return res
33 | }
34 |
35 | func CreateSampleErrorFromDiscriminatorValue(parseNode serialization.ParseNode) (serialization.Parsable, error) {
36 | res := SampleError{}
37 | return &res, nil
38 | }
39 |
--------------------------------------------------------------------------------
/internal/invalid_user_response.go:
--------------------------------------------------------------------------------
1 | package internal
2 |
3 | // InvalidUsersResponse
4 | type InvalidUsersResponse struct {
5 | // Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.
6 | additionalData map[string]interface{}
7 | //
8 | nextLink *string
9 | //
10 | value []User
11 | }
12 |
13 | // NewInvalidUsersResponse instantiates a new InvalidUsersResponse and sets the default values.
14 | func NewInvalidUsersResponse() *InvalidUsersResponse {
15 | m := &InvalidUsersResponse{}
16 | m.SetAdditionalData(make(map[string]interface{}))
17 | return m
18 | }
19 |
20 | // GetAdditionalData gets the additionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.
21 | func (m *InvalidUsersResponse) GetAdditionalData() map[string]interface{} {
22 | if m == nil {
23 | return nil
24 | } else {
25 | return m.additionalData
26 | }
27 | }
28 |
29 | // GetNextLink gets the @odata.nextLink property value.
30 | func (m *InvalidUsersResponse) GetNextLink() *string {
31 | if m == nil {
32 | return nil
33 | } else {
34 | return m.nextLink
35 | }
36 | }
37 |
38 | // GetValue gets the value property value.
39 | func (m *InvalidUsersResponse) GetValue() []User {
40 | if m == nil {
41 | return nil
42 | } else {
43 | return m.value
44 | }
45 | }
46 |
47 | func (m *InvalidUsersResponse) IsNil() bool {
48 | return m == nil
49 | }
50 |
51 | // SetAdditionalData sets the additionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.
52 | func (m *InvalidUsersResponse) SetAdditionalData(value map[string]interface{}) {
53 | if m != nil {
54 | m.additionalData = value
55 | }
56 | }
57 |
58 | // SetNextLink sets the @odata.nextLink property value.
59 | func (m *InvalidUsersResponse) SetNextLink(value *string) {
60 | if m != nil {
61 | m.nextLink = value
62 | }
63 | }
64 |
65 | // SetValue sets the value property value.
66 | func (m *InvalidUsersResponse) SetValue(value []User) {
67 | if m != nil {
68 | m.value = value
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/internal/test_byte_stream.go:
--------------------------------------------------------------------------------
1 | package internal
2 |
3 | import (
4 | "github.com/microsoft/kiota-abstractions-go/serialization"
5 | "io/fs"
6 | "os"
7 | "time"
8 | )
9 |
10 | type MockByteStream struct {
11 | Content []byte
12 | }
13 |
14 | func (m *MockByteStream) ReadAt(p []byte, off int64) (n int, err error) {
15 | v := copy(p, m.Content[off:])
16 | return v, nil
17 | }
18 |
19 | func (m *MockByteStream) Stat() (os.FileInfo, error) {
20 | return &fakeFileInfo{
21 | dir: false,
22 | basename: "mockByteStream",
23 | modtime: time.Time{},
24 | ents: nil,
25 | contents: string(m.Content),
26 | err: nil,
27 | }, nil
28 | }
29 |
30 | type fakeFileInfo struct {
31 | dir bool
32 | basename string
33 | modtime time.Time
34 | ents []*fakeFileInfo
35 | contents string
36 | err error
37 | }
38 |
39 | func (f *fakeFileInfo) Name() string { return f.basename }
40 | func (f *fakeFileInfo) Sys() any { return nil }
41 | func (f *fakeFileInfo) ModTime() time.Time { return f.modtime }
42 | func (f *fakeFileInfo) IsDir() bool { return f.dir }
43 | func (f *fakeFileInfo) Size() int64 { return int64(len(f.contents)) }
44 | func (f *fakeFileInfo) Mode() fs.FileMode {
45 | if f.dir {
46 | return 0755 | fs.ModeDir
47 | }
48 | return 0644
49 | }
50 |
51 | type UploadResponse struct {
52 | // Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.
53 | additionalData map[string]interface{}
54 | }
55 |
56 | func (s *UploadResponse) Serialize(writer serialization.SerializationWriter) error {
57 | return nil
58 | }
59 |
60 | func (s *UploadResponse) GetFieldDeserializers() map[string]func(serialization.ParseNode) error {
61 | return make(map[string]func(serialization.ParseNode) error)
62 | }
63 |
64 | func CreateUploadResponseFromDiscriminatorValue(parseNode serialization.ParseNode) (serialization.Parsable, error) {
65 | res := UploadResponse{}
66 | return &res, nil
67 | }
68 |
69 | type UploadResponseble interface {
70 | Serialize(writer serialization.SerializationWriter) error
71 | GetFieldDeserializers() map[string]func(serialization.ParseNode) error
72 | }
73 |
--------------------------------------------------------------------------------
/internal/user.go:
--------------------------------------------------------------------------------
1 | package internal
2 |
3 | import (
4 | i336074805fc853987abe6f7fe3ad97a6a6f3077a16391fec744f671a015fbd7e "time"
5 |
6 | i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55 "github.com/microsoft/kiota-abstractions-go/serialization"
7 | )
8 |
9 | type User struct {
10 | DisplayName *string
11 | DirectoryObject
12 | }
13 |
14 | func (u *User) GetDisplayName() *string {
15 | return u.DisplayName
16 | }
17 |
18 | var displayName = "A User"
19 |
20 | func NewUser() *User {
21 | return &User{
22 | DisplayName: &displayName,
23 | }
24 | }
25 |
26 | type DirectoryObject struct {
27 | Entity
28 | //
29 | deletedDateTime *i336074805fc853987abe6f7fe3ad97a6a6f3077a16391fec744f671a015fbd7e.Time
30 | }
31 |
32 | // NewDirectoryObject instantiates a new directoryObject and sets the default values.
33 | func NewDirectoryObject() *DirectoryObject {
34 | m := &DirectoryObject{
35 | Entity: *NewEntity(),
36 | }
37 | return m
38 | }
39 |
40 | // GetDeletedDateTime gets the deletedDateTime property value.
41 | func (m *DirectoryObject) GetDeletedDateTime() *i336074805fc853987abe6f7fe3ad97a6a6f3077a16391fec744f671a015fbd7e.Time {
42 | if m == nil {
43 | return nil
44 | } else {
45 | return m.deletedDateTime
46 | }
47 | }
48 |
49 | // GetFieldDeserializers the deserialization information for the current model
50 | func (m *DirectoryObject) GetFieldDeserializers() map[string]func(i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.ParseNode) error {
51 | res := m.Entity.GetFieldDeserializers()
52 | res["deletedDateTime"] = func(n i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.ParseNode) error {
53 | val, err := n.GetTimeValue()
54 | if err != nil {
55 | return err
56 | }
57 | if val != nil {
58 | m.SetDeletedDateTime(val)
59 | }
60 | return nil
61 | }
62 | return res
63 | }
64 | func (m *DirectoryObject) IsNil() bool {
65 | return m == nil
66 | }
67 |
68 | // Serialize serializes information the current object
69 | func (m *DirectoryObject) Serialize(writer i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.SerializationWriter) error {
70 | err := m.Entity.Serialize(writer)
71 | if err != nil {
72 | return err
73 | }
74 | {
75 | err = writer.WriteTimeValue("deletedDateTime", m.GetDeletedDateTime())
76 | if err != nil {
77 | return err
78 | }
79 | }
80 | return nil
81 | }
82 |
83 | // SetDeletedDateTime sets the deletedDateTime property value.
84 | func (m *DirectoryObject) SetDeletedDateTime(value *i336074805fc853987abe6f7fe3ad97a6a6f3077a16391fec744f671a015fbd7e.Time) {
85 | if m != nil {
86 | m.deletedDateTime = value
87 | }
88 | }
89 |
90 | // Entity
91 | type Entity struct {
92 | // Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.
93 | additionalData map[string]interface{}
94 | // Read-only.
95 | id *string
96 | }
97 |
98 | // NewEntity instantiates a new entity and sets the default values.
99 | func NewEntity() *Entity {
100 | m := &Entity{}
101 | m.SetAdditionalData(make(map[string]interface{}))
102 | return m
103 | }
104 |
105 | // GetAdditionalData gets the additionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.
106 | func (m *Entity) GetAdditionalData() map[string]interface{} {
107 | if m == nil {
108 | return nil
109 | } else {
110 | return m.additionalData
111 | }
112 | }
113 |
114 | // GetId gets the id property value. Read-only.
115 | func (m *Entity) GetId() *string {
116 | if m == nil {
117 | return nil
118 | } else {
119 | return m.id
120 | }
121 | }
122 |
123 | // GetFieldDeserializers the deserialization information for the current model
124 | func (m *Entity) GetFieldDeserializers() map[string]func(i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.ParseNode) error {
125 | res := make(map[string]func(i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.ParseNode) error)
126 | res["id"] = func(n i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.ParseNode) error {
127 | val, err := n.GetStringValue()
128 | if err != nil {
129 | return err
130 | }
131 | if val != nil {
132 | m.SetId(val)
133 | }
134 | return nil
135 | }
136 | return res
137 | }
138 | func (m *Entity) IsNil() bool {
139 | return m == nil
140 | }
141 |
142 | // Serialize serializes information the current object
143 | func (m *Entity) Serialize(writer i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.SerializationWriter) error {
144 | {
145 | err := writer.WriteStringValue("id", m.GetId())
146 | if err != nil {
147 | return err
148 | }
149 | }
150 | {
151 | err := writer.WriteAdditionalData(m.GetAdditionalData())
152 | if err != nil {
153 | return err
154 | }
155 | }
156 | return nil
157 | }
158 |
159 | // SetAdditionalData sets the additionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.
160 | func (m *Entity) SetAdditionalData(value map[string]interface{}) {
161 | if m != nil {
162 | m.additionalData = value
163 | }
164 | }
165 |
166 | // SetId sets the id property value. Read-only.
167 | func (m *Entity) SetId(value *string) {
168 | if m != nil {
169 | m.id = value
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/internal/user_delta_response.go:
--------------------------------------------------------------------------------
1 | package internal
2 |
3 | import (
4 | i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55 "github.com/microsoft/kiota-abstractions-go/serialization"
5 | )
6 |
7 | // UsersDeltaResponse
8 | type UsersDeltaResponse struct {
9 | //
10 | UsersResponse
11 | //
12 | odataDeltaLink *string
13 | }
14 |
15 | // NewDeltasResponse instantiates a new usersResponse and sets the default values.
16 | func NewUsersDeltaResponse() *UsersDeltaResponse {
17 | m := &UsersDeltaResponse{
18 | UsersResponse: *NewUsersResponse(),
19 | }
20 | return m
21 | }
22 |
23 | // GetOdataDeltaLink gets the @odata.nextLink property value.
24 | func (m *UsersDeltaResponse) GetOdataDeltaLink() *string {
25 | if m == nil {
26 | return nil
27 | } else {
28 | return m.odataDeltaLink
29 | }
30 | }
31 |
32 | // GetFieldDeserializers the deserialization information for the current model
33 | func (m *UsersDeltaResponse) GetFieldDeserializers() map[string]func(i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.ParseNode) error {
34 | res := m.UsersResponse.GetFieldDeserializers()
35 | res["@odata.deltaLink"] = func(n i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.ParseNode) error {
36 | val, err := n.GetStringValue()
37 | if err != nil {
38 | return err
39 | }
40 | if val != nil {
41 | m.SetOdataDeltaLink(val)
42 | }
43 | return nil
44 | }
45 | return res
46 | }
47 |
48 | // Serialize serializes information the current object
49 | func (m *UsersDeltaResponse) Serialize(writer i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.SerializationWriter) error {
50 | {
51 | err := writer.WriteStringValue("@odata.deltaLink", m.GetOdataDeltaLink())
52 | if err != nil {
53 | return err
54 | }
55 | }
56 |
57 | return m.UsersResponse.Serialize(writer)
58 | }
59 |
60 | // SetOdataDeltaLink sets the @odata.deltaLink property value.
61 | func (m *UsersDeltaResponse) SetOdataDeltaLink(value *string) {
62 | if m != nil {
63 | m.odataDeltaLink = value
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/internal/user_response.go:
--------------------------------------------------------------------------------
1 | package internal
2 |
3 | import (
4 | i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55 "github.com/microsoft/kiota-abstractions-go/serialization"
5 | )
6 |
7 | // UsersResponse
8 | type UsersResponse struct {
9 | // Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.
10 | additionalData map[string]interface{}
11 | //
12 | odataNextLink *string
13 | //
14 | value []User
15 | }
16 |
17 | // NewUsersResponse instantiates a new usersResponse and sets the default values.
18 | func NewUsersResponse() *UsersResponse {
19 | m := &UsersResponse{}
20 | m.SetAdditionalData(make(map[string]interface{}))
21 | return m
22 | }
23 |
24 | // GetAdditionalData gets the additionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.
25 | func (m *UsersResponse) GetAdditionalData() map[string]interface{} {
26 | if m == nil {
27 | return nil
28 | } else {
29 | return m.additionalData
30 | }
31 | }
32 |
33 | // GetOdataNextLink gets the @odata.nextLink property value.
34 | func (m *UsersResponse) GetOdataNextLink() *string {
35 | if m == nil {
36 | return nil
37 | } else {
38 | return m.odataNextLink
39 | }
40 | }
41 |
42 | // GetValue gets the value property value.
43 | func (m *UsersResponse) GetValue() []User {
44 | if m == nil {
45 | return nil
46 | } else {
47 | return m.value
48 | }
49 | }
50 |
51 | // GetFieldDeserializers the deserialization information for the current model
52 | func (m *UsersResponse) GetFieldDeserializers() map[string]func(i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.ParseNode) error {
53 | res := make(map[string]func(i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.ParseNode) error)
54 | res["@odata.nextLink"] = func(n i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.ParseNode) error {
55 | val, err := n.GetStringValue()
56 | if err != nil {
57 | return err
58 | }
59 | if val != nil {
60 | m.SetOdataNextLink(val)
61 | }
62 | return nil
63 | }
64 | res["value"] = func(n i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.ParseNode) error {
65 | val, err := n.GetCollectionOfObjectValues(func(pn i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.ParseNode) (i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.Parsable, error) {
66 | return NewUser(), nil
67 | })
68 | if err != nil {
69 | return err
70 | }
71 | if val != nil {
72 | res := make([]User, len(val))
73 | for i, v := range val {
74 | res[i] = *(v.(*User))
75 | }
76 | m.SetValue(res)
77 | }
78 | return nil
79 | }
80 | return res
81 | }
82 | func (m *UsersResponse) IsNil() bool {
83 | return m == nil
84 | }
85 |
86 | // Serialize serializes information the current object
87 | func (m *UsersResponse) Serialize(writer i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.SerializationWriter) error {
88 | {
89 | err := writer.WriteStringValue("@odata.nextLink", m.GetOdataNextLink())
90 | if err != nil {
91 | return err
92 | }
93 | }
94 | {
95 | cast := make([]i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.Parsable, len(m.GetValue()))
96 | for i, v := range m.GetValue() {
97 | temp := v
98 | cast[i] = i04eb5309aeaafadd28374d79c8471df9b267510b4dc2e3144c378c50f6fd7b55.Parsable(&temp)
99 | }
100 | err := writer.WriteCollectionOfObjectValues("value", cast)
101 | if err != nil {
102 | return err
103 | }
104 | }
105 | {
106 | err := writer.WriteAdditionalData(m.GetAdditionalData())
107 | if err != nil {
108 | return err
109 | }
110 | }
111 | return nil
112 | }
113 |
114 | // SetAdditionalData sets the additionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.
115 | func (m *UsersResponse) SetAdditionalData(value map[string]interface{}) {
116 | if m != nil {
117 | m.additionalData = value
118 | }
119 | }
120 |
121 | // SetOdataNextLink sets the @odata.nextLink property value.
122 | func (m *UsersResponse) SetOdataNextLink(value *string) {
123 | if m != nil {
124 | m.odataNextLink = value
125 | }
126 | }
127 |
128 | // SetValue sets the value property value.
129 | func (m *UsersResponse) SetValue(value []User) {
130 | if m != nil {
131 | m.value = value
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/page_iterator.go:
--------------------------------------------------------------------------------
1 | package msgraphgocore
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "net/url"
7 | "reflect"
8 |
9 | abstractions "github.com/microsoft/kiota-abstractions-go"
10 | "github.com/microsoft/kiota-abstractions-go/serialization"
11 | )
12 |
13 | const PageIteratorErrorRegistryKey = "PAGE_ITERATOR_ERROR_REGISTRY_KEY"
14 |
15 | // PageIterator represents an iterator object that can be used to get subsequent pages of a collection.
16 | type PageIterator[T interface{}] struct {
17 | currentPage PageResult[T]
18 | reqAdapter abstractions.RequestAdapter
19 | pauseIndex int
20 | constructorFunc serialization.ParsableFactory
21 | headers *abstractions.RequestHeaders
22 | reqOptions []abstractions.RequestOption
23 | errorMappings abstractions.ErrorMappings
24 | }
25 |
26 | // PageResult represents a page object built from a graph response object
27 | type PageResult[T interface{}] struct {
28 | oDataNextLink *string
29 | oDataDeltaLink *string
30 | value []T
31 | }
32 |
33 | func (p *PageResult[T]) getValue() []T {
34 | if p == nil {
35 | return nil
36 | }
37 |
38 | return p.value
39 | }
40 |
41 | func (p *PageResult[T]) getOdataNextLink() *string {
42 | if p == nil {
43 | return nil
44 | }
45 |
46 | return p.oDataNextLink
47 | }
48 |
49 | // NewPageIterator creates an iterator instance
50 | //
51 | // It has three parameters. res is the graph response from the initial request and represents the first page.
52 | // reqAdapter is used for getting the next page and constructorFunc is used for serializing next page's response to the specified type.
53 | func NewPageIterator[T interface{}](res interface{}, reqAdapter abstractions.RequestAdapter, constructorFunc serialization.ParsableFactory) (*PageIterator[T], error) {
54 | if reqAdapter == nil {
55 | return nil, errors.New("reqAdapter can't be nil")
56 | }
57 |
58 | page, err := convertToPage[T](res)
59 | if err != nil {
60 | return nil, err
61 | }
62 |
63 | errorMapping := getErrorMapper(PageIteratorErrorRegistryKey)
64 |
65 | return &PageIterator[T]{
66 | currentPage: page,
67 | reqAdapter: reqAdapter,
68 | pauseIndex: 0,
69 | constructorFunc: constructorFunc,
70 | headers: abstractions.NewRequestHeaders(),
71 | errorMappings: errorMapping,
72 | }, nil
73 | }
74 |
75 | // Iterate traverses all pages and enumerates all items in the current page and returns an error if something goes wrong.
76 | //
77 | // Iterate receives a callback function which is called with each item in the current page as an argument. The callback function
78 | // returns a boolean. To traverse and enumerate all pages always return true and to pause traversal and enumeration
79 | // return false from the callback.
80 | //
81 | // Example
82 | //
83 | // pageIterator, err := NewPageIterator(resp, reqAdapter, parsableFactory)
84 | // callbackFunc := func (pageItem interface{}) bool {
85 | // fmt.Println(page item.GetDisplayName())
86 | // return true
87 | // }
88 | // err := pageIterator.Iterate(context.Background(), callbackFunc)
89 | func (pI *PageIterator[T]) Iterate(context context.Context, callback func(pageItem T) bool) error {
90 | for {
91 | keepIterating := pI.enumerate(callback)
92 |
93 | if !keepIterating {
94 | // Callback returned false, stop iterating through pages.
95 | return nil
96 | }
97 |
98 | if pI.currentPage.getOdataNextLink() == nil || *pI.currentPage.getOdataNextLink() == "" {
99 | return nil
100 | }
101 |
102 | nextPage, err := pI.next(context)
103 | if err != nil {
104 | return err
105 | }
106 |
107 | pI.currentPage = nextPage
108 | pI.pauseIndex = 0 // when moving to the next page reset pauseIndex
109 | }
110 | }
111 |
112 | // SetHeaders provides headers for requests made to get subsequent pages
113 | //
114 | // Headers in the initial request -- request to get the first page -- are not included in subsequent page requests.
115 | func (pI *PageIterator[T]) SetHeaders(headers *abstractions.RequestHeaders) {
116 | pI.headers = headers
117 | }
118 |
119 | // SetReqOptions provides configuration for handlers during requests for subsequent pages
120 | func (pI *PageIterator[T]) SetReqOptions(reqOptions []abstractions.RequestOption) {
121 | pI.reqOptions = reqOptions
122 | }
123 |
124 | // GetOdataNextLink returns the @odata.nextLink value in the current page result.
125 | func (pI *PageIterator[T]) GetOdataNextLink() *string {
126 | return pI.currentPage.oDataNextLink
127 | }
128 |
129 | // GetOdataDeltaLink returns the @odata.deltaLink value in current paged result.
130 | func (pI *PageIterator[T]) GetOdataDeltaLink() *string {
131 | return pI.currentPage.oDataDeltaLink
132 | }
133 |
134 | func (pI *PageIterator[T]) next(context context.Context) (PageResult[T], error) {
135 | var page PageResult[T]
136 |
137 | resp, err := pI.fetchNextPage(context)
138 | if err != nil {
139 | return page, err
140 | }
141 |
142 | page, err = convertToPage[T](resp)
143 | if err != nil {
144 | return page, err
145 | }
146 |
147 | return page, nil
148 | }
149 |
150 | func (pI *PageIterator[T]) fetchNextPage(context context.Context) (serialization.Parsable, error) {
151 | var graphResponse serialization.Parsable
152 | var err error
153 |
154 | if pI.currentPage.getOdataNextLink() == nil {
155 | return graphResponse, nil
156 | }
157 |
158 | nextLink, err := url.Parse(*pI.currentPage.getOdataNextLink())
159 | if err != nil {
160 | return nil, errors.New("parsing nextLink url failed")
161 | }
162 |
163 | requestInfo := abstractions.NewRequestInformation()
164 | requestInfo.Method = abstractions.GET
165 | requestInfo.SetUri(*nextLink)
166 | requestInfo.Headers.AddAll(pI.headers)
167 | requestInfo.AddRequestOptions(pI.reqOptions)
168 |
169 | graphResponse, err = pI.reqAdapter.Send(context, requestInfo, pI.constructorFunc, pI.errorMappings)
170 | if err != nil {
171 | return nil, err
172 | }
173 |
174 | return graphResponse, nil
175 | }
176 |
177 | func (pI *PageIterator[T]) enumerate(callback func(item T) bool) bool {
178 | keepIterating := true
179 |
180 | pageItems := pI.currentPage.getValue()
181 | if pageItems == nil {
182 | return false
183 | }
184 |
185 | // the current page has no items to enumerate
186 | if pI.currentPage.getValue() == nil {
187 | return false
188 | }
189 |
190 | // start/continue enumerating page items from pauseIndex.
191 | // this makes it possible to resume iteration from where we paused iteration.
192 | for i := pI.pauseIndex; i < len(pageItems); i++ {
193 | keepIterating = callback(pageItems[i])
194 |
195 | // Set pauseIndex so that we know where to resume from.
196 | // Resumes from the next item
197 | pI.pauseIndex = i + 1
198 |
199 | if !keepIterating {
200 | break
201 | }
202 | }
203 |
204 | return keepIterating
205 | }
206 |
207 | // PageWithOdataNextLink represents a contract with the GetOdataNextLink() method
208 | type PageWithOdataNextLink interface {
209 | GetOdataNextLink() *string
210 | }
211 |
212 | // PageWithOdataDeltaLink represents a contract with the GetOdataDeltaLink() method
213 | type PageWithOdataDeltaLink interface {
214 | GetOdataDeltaLink() *string
215 | }
216 |
217 | func convertToPage[T interface{}](response interface{}) (PageResult[T], error) {
218 | var page PageResult[T]
219 |
220 | if response == nil {
221 | return page, errors.New("response cannot be nil")
222 | }
223 |
224 | method := reflect.ValueOf(response).MethodByName("GetValue")
225 | if method.Kind() == reflect.Invalid {
226 | return page, errors.New("value property missing in response object")
227 | }
228 | value := method.Call(nil)[0]
229 |
230 | // Collect all entities in the value slice.
231 | // This converts a graph slice ie []graph.User to a dynamic slice []interface{}
232 | collected := make([]T, 0)
233 | for i := 0; i < value.Len(); i++ {
234 | collected = append(collected, value.Index(i).Interface().(T))
235 | }
236 |
237 | parsablePage, ok := response.(PageWithOdataNextLink)
238 | if !ok {
239 | return page, errors.New("response does not have next link accessor")
240 | }
241 |
242 | deltablePage, ok := response.(PageWithOdataDeltaLink)
243 | if ok {
244 | page.oDataDeltaLink = deltablePage.GetOdataDeltaLink()
245 | }
246 |
247 | page.oDataNextLink = parsablePage.GetOdataNextLink()
248 | page.value = collected
249 |
250 | return page, nil
251 | }
252 |
--------------------------------------------------------------------------------
/page_iterator_test.go:
--------------------------------------------------------------------------------
1 | package msgraphgocore
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 | "github.com/stretchr/testify/require"
8 | nethttp "net/http"
9 | httptest "net/http/httptest"
10 | testing "testing"
11 |
12 | abstractions "github.com/microsoft/kiota-abstractions-go"
13 | "github.com/microsoft/kiota-abstractions-go/authentication"
14 | "github.com/microsoft/kiota-abstractions-go/serialization"
15 | jsonserialization "github.com/microsoft/kiota-serialization-json-go"
16 | "github.com/microsoftgraph/msgraph-sdk-go-core/internal"
17 | "github.com/stretchr/testify/assert"
18 | )
19 |
20 | func init() {
21 | abstractions.RegisterDefaultSerializer(func() serialization.SerializationWriterFactory {
22 | return jsonserialization.NewJsonSerializationWriterFactory()
23 | })
24 | abstractions.RegisterDefaultDeserializer(func() serialization.ParseNodeFactory {
25 | return jsonserialization.NewJsonParseNodeFactory()
26 | })
27 | }
28 |
29 | var reqAdapter, _ = NewGraphRequestAdapterBase(&authentication.AnonymousAuthenticationProvider{}, GraphClientOptions{
30 | GraphServiceVersion: "",
31 | GraphServiceLibraryVersion: "",
32 | })
33 |
34 | func ParsableCons(pn serialization.ParseNode) (serialization.Parsable, error) {
35 | return internal.NewUsersResponse(), nil
36 | }
37 |
38 | func ParsableDeltaCons(pn serialization.ParseNode) (serialization.Parsable, error) {
39 | return internal.NewUsersDeltaResponse(), nil
40 | }
41 |
42 | func TestConstructorWithInvalidRequestAdapter(t *testing.T) {
43 | graphResponse := internal.NewUsersResponse()
44 |
45 | _, err := NewPageIterator[internal.User](graphResponse, nil, ParsableCons)
46 |
47 | assert.NotNil(t, err)
48 | }
49 |
50 | func TestConstructorWithInvalidGraphResponse(t *testing.T) {
51 | graphResponse := internal.NewInvalidUsersResponse()
52 |
53 | _, err := NewPageIterator[internal.User](graphResponse, reqAdapter, ParsableCons)
54 |
55 | assert.NotNil(t, err)
56 | }
57 |
58 | func TestConstructorWithInvalidUserGraphResponse(t *testing.T) {
59 | graphResponse := internal.NewInvalidUsersResponse()
60 |
61 | nextLink := "next-page"
62 | users := make([]internal.User, 0)
63 |
64 | graphResponse.SetNextLink(&nextLink)
65 | graphResponse.SetValue(users)
66 |
67 | _, err := NewPageIterator[internal.User](graphResponse, reqAdapter, ParsableCons)
68 |
69 | assert.NotNil(t, err)
70 | }
71 |
72 | func TestPageIteratorHandlesHTTPError(t *testing.T) {
73 | errorMapping := abstractions.ErrorMappings{
74 | "4XX": internal.CreateSampleErrorFromDiscriminatorValue,
75 | "5XX": internal.CreateSampleErrorFromDiscriminatorValue,
76 | }
77 | // register errorMapper
78 | err := RegisterError(PageIteratorErrorRegistryKey, errorMapping)
79 | require.NoError(t, err)
80 |
81 | testServer := httptest.NewServer(nethttp.HandlerFunc(func(w nethttp.ResponseWriter, req *nethttp.Request) {
82 | w.Header().Set("Content-Type", "application/json")
83 | w.WriteHeader(403)
84 | fmt.Fprint(w, "{}")
85 | }))
86 | defer testServer.Close()
87 |
88 | graphResponse := buildGraphResponse()
89 | mockPath := testServer.URL + "/next-page"
90 | graphResponse.SetOdataNextLink(&mockPath)
91 |
92 | pageIterator, _ := NewPageIterator[internal.User](graphResponse, reqAdapter, ParsableCons)
93 | headers := abstractions.NewRequestHeaders()
94 | headers.Add("ConsistencyLevel", "eventual")
95 | pageIterator.SetHeaders(headers)
96 | res := make([]string, 0)
97 |
98 | err = pageIterator.Iterate(context.Background(), func(item internal.User) bool {
99 | res = append(res, *item.GetDisplayName())
100 | return true
101 | })
102 |
103 | var sampleError *internal.SampleError
104 | switch {
105 | case errors.As(err, &sampleError):
106 | assert.Equal(t, "error status code received from the API", err.Error())
107 | default:
108 | assert.Fail(t, "error type is not as expected")
109 | }
110 |
111 | err = DeRegisterError(PageIteratorErrorRegistryKey)
112 | require.NoError(t, err)
113 | }
114 |
115 | func TestIterateStopsWhenCallbackReturnsFalse(t *testing.T) {
116 | res := make([]string, 0)
117 | graphResponse := buildGraphResponse()
118 | testServer := httptest.NewServer(nethttp.HandlerFunc(func(w nethttp.ResponseWriter, req *nethttp.Request) {
119 | w.Header().Set("Content-Type", "application/json")
120 | fmt.Fprint(w, `
121 | {
122 | "@odata.nextLink": "",
123 | "value": [
124 | {
125 | "id": "10"
126 | }
127 | ]
128 | }
129 | `)
130 | assert.NotNil(t, req.Header["ConsistencyLevel"])
131 | }))
132 | defer testServer.Close()
133 | pageIterator, _ := NewPageIterator[internal.User](graphResponse, reqAdapter, ParsableCons)
134 | headers := abstractions.NewRequestHeaders()
135 | headers.Add("ConsistencyLevel", "eventual")
136 | pageIterator.SetHeaders(headers)
137 |
138 | err := pageIterator.Iterate(context.Background(), func(item internal.User) bool {
139 | res = append(res, *item.GetDisplayName())
140 | return !(*item.GetId() == "2")
141 | })
142 | if err != nil {
143 | t.Error(err)
144 | }
145 |
146 | assert.Equal(t, len(res), 3)
147 | }
148 |
149 | func TestIterateEnumeratesAllPages(t *testing.T) {
150 | testServer := httptest.NewServer(nethttp.HandlerFunc(func(w nethttp.ResponseWriter, req *nethttp.Request) {
151 | w.Header().Set("Content-Type", "application/json")
152 | fmt.Fprint(w, `
153 | {
154 | "@odata.nextLink": "",
155 | "value": [
156 | {
157 | "id": "10"
158 | }
159 | ]
160 | }
161 | `)
162 |
163 | }))
164 | defer testServer.Close()
165 |
166 | graphResponse := buildGraphResponse()
167 | mockPath := testServer.URL + "/next-page"
168 | graphResponse.SetOdataNextLink(&mockPath)
169 |
170 | pageIterator, _ := NewPageIterator[internal.User](graphResponse, reqAdapter, ParsableCons)
171 | res := make([]string, 0)
172 |
173 | err := pageIterator.Iterate(context.Background(), func(item internal.User) bool {
174 | res = append(res, *item.GetId())
175 | return true
176 | })
177 |
178 | // Initial page has 5 items and the next page has 1 item.
179 | assert.Equal(t, len(res), 6)
180 | assert.Nil(t, err)
181 | }
182 |
183 | func TestIterateCanBePausedAndResumed(t *testing.T) {
184 | res := make([]string, 0)
185 | res2 := make([]string, 0)
186 |
187 | testServer := httptest.NewServer(nethttp.HandlerFunc(func(w nethttp.ResponseWriter, req *nethttp.Request) {
188 | w.Header().Set("Content-Type", "application/json")
189 | fmt.Fprint(w, `
190 | {
191 | "@odata.nextLink": "",
192 | "value": [
193 | {
194 | "id": "10"
195 | }
196 | ]
197 | }
198 | `)
199 |
200 | }))
201 | defer testServer.Close()
202 |
203 | response := buildGraphResponse()
204 | mockPath := testServer.URL + "/next-page"
205 | response.SetOdataNextLink(&mockPath)
206 |
207 | pageIterator, _ := NewPageIterator[internal.User](response, reqAdapter, ParsableCons)
208 | pageIterator.Iterate(context.Background(), func(item internal.User) bool {
209 | res = append(res, *item.GetId())
210 |
211 | return *item.GetId() != "4"
212 | })
213 |
214 | assert.Equal(t, res, []string{"0", "1", "2", "3", "4"})
215 | assert.Equal(t, pageIterator.GetOdataNextLink(), response.GetOdataNextLink())
216 |
217 | pageIterator.Iterate(context.Background(), func(item internal.User) bool {
218 | res2 = append(res2, *item.GetId())
219 |
220 | return true
221 | })
222 | assert.Equal(t, res2, []string{"10"})
223 | assert.Empty(t, pageIterator.GetOdataNextLink())
224 |
225 | pageIterator.Iterate(context.Background(), func(item internal.User) bool {
226 | assert.Fail(t, "Should not re-iterate over items")
227 | return true
228 | })
229 | }
230 |
231 | func TestGetOdataNextLink(t *testing.T) {
232 | testServer := httptest.NewServer(nethttp.HandlerFunc(func(w nethttp.ResponseWriter, req *nethttp.Request) {
233 | w.Header().Set("Content-Type", "application/json")
234 | fmt.Fprint(w, `
235 | {
236 | "@odata.nextLink": "",
237 | "value": [
238 | {
239 | "id": "10"
240 | }
241 | ]
242 | }
243 | `)
244 | }))
245 | defer testServer.Close()
246 |
247 | graphResponse := buildGraphResponse()
248 | mockPath := testServer.URL + "/next-page"
249 | graphResponse.SetOdataNextLink(&mockPath)
250 |
251 | pageIterator, _ := NewPageIterator[internal.User](graphResponse, reqAdapter, ParsableDeltaCons)
252 | pageIterator.Iterate(context.Background(), func(item internal.User) bool {
253 | return true
254 | })
255 |
256 | assert.Empty(t, pageIterator.GetOdataNextLink())
257 | }
258 |
259 | func TestGetOdataDeltaLink(t *testing.T) {
260 | testServer := httptest.NewServer(nethttp.HandlerFunc(func(w nethttp.ResponseWriter, req *nethttp.Request) {
261 | w.Header().Set("Content-Type", "application/json")
262 | fmt.Fprint(w, `
263 | {
264 | "@odata.nextLink": "",
265 | "@odata.deltaLink": "delta-page-2",
266 | "value": [
267 | {
268 | "id": "10"
269 | }
270 | ]
271 | }
272 | `)
273 | }))
274 | defer testServer.Close()
275 |
276 | dl := "delta-page-1"
277 | mockPath := testServer.URL + "/next-page"
278 |
279 | graphResponse := &internal.UsersDeltaResponse{
280 | UsersResponse: *buildGraphResponse(),
281 | }
282 | graphResponse.SetOdataDeltaLink(&dl)
283 | graphResponse.SetOdataNextLink(&mockPath)
284 |
285 | pageIterator, _ := NewPageIterator[internal.User](graphResponse, reqAdapter, ParsableDeltaCons)
286 | pageIterator.Iterate(context.Background(), func(item internal.User) bool {
287 | return true
288 | })
289 |
290 | assert.Equal(t, *pageIterator.GetOdataDeltaLink(), "delta-page-2")
291 | }
292 |
293 | func buildGraphResponse() *internal.UsersResponse {
294 | var res = internal.NewUsersResponse()
295 |
296 | nextLink := "next-page"
297 | users := make([]internal.User, 0)
298 |
299 | for i := 0; i < 5; i++ {
300 | u := internal.NewUser()
301 | id := fmt.Sprint(i)
302 | u.SetId(&id)
303 |
304 | users = append(users, *u)
305 | }
306 |
307 | res.SetOdataNextLink(&nextLink)
308 | res.SetValue(users)
309 |
310 | return res
311 | }
312 |
313 | func Test_convertToPage(t *testing.T) {
314 | type args struct {
315 | response interface{}
316 | }
317 | tests := []struct {
318 | name string
319 | args args
320 | want PageResult[validTestStruct]
321 | wantErr bool
322 | }{
323 | {
324 | name: "should pass",
325 | args: args{
326 | response: &validTestStruct{
327 | obj: []validTestStruct{
328 | {
329 | obj: []validTestStruct{
330 | {
331 | obj: []validTestStruct{},
332 | },
333 | },
334 | },
335 | }},
336 | },
337 | want: PageResult[validTestStruct]{
338 | oDataNextLink: nil,
339 | oDataDeltaLink: nil,
340 | value: []validTestStruct{
341 | {
342 | obj: []validTestStruct{
343 | {
344 | obj: []validTestStruct{},
345 | },
346 | },
347 | },
348 | },
349 | },
350 | },
351 | {
352 | name: "should return error 'saying response cannot be nil' for nil response",
353 | args: args{
354 | response: nil,
355 | },
356 | want: PageResult[validTestStruct]{
357 | oDataNextLink: nil,
358 | oDataDeltaLink: nil,
359 | value: nil,
360 | },
361 | wantErr: true,
362 | },
363 | {
364 | name: "should return error 'value property missing in response object' for missing 'GetValue' method",
365 | args: args{
366 | response: &invalidTestStruct{
367 | obj: []invalidTestStruct{
368 | {
369 | obj: []invalidTestStruct{
370 | {
371 | obj: []invalidTestStruct{},
372 | },
373 | },
374 | },
375 | }},
376 | },
377 | want: PageResult[validTestStruct]{
378 | oDataNextLink: nil,
379 | oDataDeltaLink: nil,
380 | },
381 | wantErr: true,
382 | },
383 | {
384 | name: "should return error 'response does not have next link accessor' for missing 'GetOdataNextLink() *string' method",
385 | args: args{
386 | response: &invalidTestStruct{
387 | obj: []invalidTestStruct{
388 | {
389 | obj: []invalidTestStruct{
390 | {
391 | obj: []invalidTestStruct{},
392 | },
393 | },
394 | },
395 | }},
396 | },
397 | want: PageResult[validTestStruct]{
398 | oDataNextLink: nil,
399 | oDataDeltaLink: nil,
400 | },
401 | wantErr: true,
402 | },
403 | }
404 | for _, tt := range tests {
405 | t.Run(tt.name, func(t *testing.T) {
406 | got, err := convertToPage[validTestStruct](tt.args.response)
407 | if (err != nil) != tt.wantErr {
408 | t.Errorf("convertToPage() error = %v, wantErr %v", err, tt.wantErr)
409 | return
410 | }
411 | assert.Equal(t, tt.want.oDataDeltaLink, got.oDataDeltaLink, "got %v, want %v", got.oDataNextLink, tt.want.oDataNextLink)
412 | assert.Equal(t, tt.want.oDataNextLink, got.oDataNextLink, "got %v, want %v", got.oDataDeltaLink, tt.want.oDataDeltaLink)
413 | assert.Equal(t, tt.want.value, got.value, "got %v, want %v", got.value, tt.want.value)
414 | })
415 | }
416 | }
417 |
418 | type validTestStruct struct {
419 | obj []validTestStruct
420 | }
421 |
422 | func (t *validTestStruct) GetValue() []validTestStruct {
423 | return t.obj
424 | }
425 |
426 | func (t *validTestStruct) GetOdataNextLink() *string {
427 | return nil
428 | }
429 |
430 | func (t *validTestStruct) GetOdataDeltaLink() *string {
431 | return nil
432 | }
433 |
434 | type invalidTestStruct struct {
435 | obj []invalidTestStruct
436 | }
437 |
438 | func (t *invalidTestStruct) GetOdataDeltaLink() *string {
439 | return nil
440 | }
441 |
--------------------------------------------------------------------------------
/release-please-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "release-type": "go",
3 | "bump-minor-pre-major": true,
4 | "bump-patch-for-minor-pre-major": true,
5 | "include-component-in-tag": false,
6 | "include-v-in-tag": true,
7 | "packages": {
8 | ".": {
9 | "package-name": "github.com/microsoftgraph/msgraph-sdk-go-core",
10 | "changelog-path": "CHANGELOG.md",
11 | "extra-files": [
12 | "version.go"
13 | ]
14 | }
15 | },
16 | "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json"
17 | }
18 |
--------------------------------------------------------------------------------
/sonar-project.properties:
--------------------------------------------------------------------------------
1 | sonar.projectKey=microsoftgraph_msgraph-sdk-go-core
2 | sonar.organization=microsoftgraph2
3 | sonar.exclusions=**/*_test.go
4 | sonar.test.inclusions=**/*_test.go
5 | sonar.go.tests.reportPaths=result.out
6 | sonar.go.coverage.reportPaths=cover.out
--------------------------------------------------------------------------------
/version.go:
--------------------------------------------------------------------------------
1 | package msgraphgocore
2 |
3 | /** The SDK version */
4 | // x-release-please-start-version
5 | var CoreVersion = "1.3.2"
6 |
7 | // x-release-please-end
8 |
--------------------------------------------------------------------------------