Full Code of containers/oci-spec-rs for AI

main 926197a42cf9 cached
67 files
410.3 KB
105.4k tokens
449 symbols
1 requests
Download .txt
Showing preview only (431K chars total). Download the full file or copy to clipboard to get everything.
Repository: containers/oci-spec-rs
Branch: main
Commit: 926197a42cf9
Files: 67
Total size: 410.3 KB

Directory structure:
gitextract_jzfwglfk/

├── .codecov.yml
├── .devcontainer/
│   └── devcontainer.json
├── .github/
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── dependabot.yml
│   ├── grcov.yml
│   ├── release.yml
│   └── workflows/
│       ├── ci.yml
│       ├── cross.yml
│       ├── gh-pages.yml
│       ├── release.yml
│       └── security-audit.yml
├── .gitignore
├── .rustfmt.toml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Cargo.toml
├── GOVERNANCE.md
├── LICENSE
├── MAINTAINERS.md
├── OWNERS
├── OWNERS_ALIASES
├── README.md
├── SECURITY.md
├── SECURITY_CONTACTS
├── hack/
│   └── release
├── release.md
├── src/
│   ├── distribution/
│   │   ├── error.rs
│   │   ├── mod.rs
│   │   ├── reference.rs
│   │   ├── repository.rs
│   │   ├── tag.rs
│   │   └── version.rs
│   ├── error.rs
│   ├── image/
│   │   ├── annotations.rs
│   │   ├── artifact.rs
│   │   ├── config.rs
│   │   ├── descriptor.rs
│   │   ├── digest.rs
│   │   ├── index.rs
│   │   ├── manifest.rs
│   │   ├── mod.rs
│   │   ├── oci_layout.rs
│   │   └── version.rs
│   ├── lib.rs
│   └── runtime/
│       ├── capability.rs
│       ├── features.rs
│       ├── hooks.rs
│       ├── linux.rs
│       ├── miscellaneous.rs
│       ├── mod.rs
│       ├── process.rs
│       ├── solaris.rs
│       ├── state.rs
│       ├── test/
│       │   └── fixture/
│       │       ├── sample.json
│       │       ├── sample_state.json
│       │       ├── sample_windows.json
│       │       └── sample_zos.json
│       ├── test.rs
│       ├── version.rs
│       ├── vm.rs
│       ├── windows.rs
│       └── zos.rs
└── test/
    └── data/
        ├── artifact_manifest.json
        ├── config.json
        ├── index.json
        ├── manifest.json
        └── oci-layout

================================================
FILE CONTENTS
================================================

================================================
FILE: .codecov.yml
================================================
---
codecov:
  notify:
    after_n_builds: 1
    require_ci_to_pass: false

coverage:
  precision: 2
  round: down
  range: 50..75

comment:
  layout: "header, diff"
  behavior: default
  require_changes: false


================================================
FILE: .devcontainer/devcontainer.json
================================================
{
	"name": "oci-spec-rs",
	"image": "mcr.microsoft.com/devcontainers/rust:1-bullseye",
	"customizations": {
		"vscode": {
			"settings": {
				"files.watcherExclude": {
					"**/target/**": true
				},
				"rust-analyzer.checkOnSave.command": "clippy"
			},
			"extensions": [
				"rust-lang.rust-analyzer",
				"tamasfe.even-better-toml"
			]
		}
	}
}

================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!--  Thanks for sending a pull request!

Please be aware that we're following the Kubernetes guidelines of contributing
to this project. This means that we have to use this mandatory template for all
of our pull requests.

Please also make sure you've read and understood our contributing guidelines
(https://github.com/containers/oci-spec-rs/blob/main/CONTRIBUTING.md) as well as
ensuring that all your commits are signed with `git commit -s`.

Here are some additional tips for you:

- If this is your first time, please read our contributor guidelines:
  https://git.k8s.io/community/contributors/guide#your-first-contribution and
  developer guide
  https://git.k8s.io/community/contributors/devel/development.md#development-guide
- Please label this pull request according to what type of issue you are
  addressing, especially if this is a release targeted pull request. For
  reference on required PR/issue labels, read here:
  https://git.k8s.io/community/contributors/devel/sig-release/release.md#issuepr-kind-label
- If you want *faster* PR reviews, read how:
  https://git.k8s.io/community/contributors/guide/pull-requests.md#best-practices-for-faster-reviews
- If the PR is unfinished, see how to mark it:
  https://git.k8s.io/community/contributors/guide/pull-requests.md#marking-unfinished-pull-requests
-->

#### What type of PR is this?

<!--
Uncomment only one `/kind <>` line, hit enter to put that in a new line, and
remove leading whitespace from that line:
-->

<!--
/kind api-change
/kind bug
/kind ci
/kind cleanup
/kind dependency-change
/kind deprecation
/kind design
/kind documentation
/kind failing-test
/kind feature
/kind flake
/kind other
-->

#### What this PR does / why we need it:

#### Which issue(s) this PR fixes:

<!--
Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->

<!--
Fixes #
or
None
-->

#### Special notes for your reviewer:

#### Does this PR introduce a user-facing change?

<!--
If no, just write `None` in the release-note block below. If yes, a release note
is required: Enter your extended release note in the block below. If the PR
requires additional action from users switching to the new release, include the
string "action required".

For more information on release notes see:
https://git.k8s.io/community/contributors/guide/release-notes.md
-->

```release-note

```


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
  - package-ecosystem: cargo
    directory: "/"
    schedule:
      interval: daily
      time: "11:00"
    open-pull-requests-limit: 10
    allow:
      - dependency-type: direct
      - dependency-type: indirect
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: daily
    open-pull-requests-limit: 10
    labels:
      - "kind/test"
    groups:
      actions:
        update-types:
          - "major"
          - "minor"
          - "patch"


================================================
FILE: .github/grcov.yml
================================================
branch: true
ignore-not-existing: true
llvm: true
filter: covered
output-type: lcov
output-path: ./lcov.info
prefix-dir: /home/user/build/


================================================
FILE: .github/release.yml
================================================
changelog:
  exclude:
    authors:
      - dependabot
  categories:
    - title: Breaking Changes
      labels:
        - breaking change
    - title: Runtime Specification
      labels:
        - kind/runtime
    - title: Image Format Specification
      labels:
        - kind/image
    - title: Distribution Specification
      labels:
        - kind/distribution
    - title: Test improvements and Misc Fixes
      labels:
        - kind/test
        - kind/cleanup
    - title: Other Changes
      labels:
        - "*"


================================================
FILE: .github/workflows/ci.yml
================================================
name: ci
on:
  pull_request: {}
  push:
    branches:
      - main
env:
  CARGO_TERM_COLOR: always
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - name: Generate lockfile
        run: cargo generate-lockfile
      - name: Setup Cache
        uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
      - run: cargo build --all-features

  doc:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - name: Generate lockfile
        run: cargo generate-lockfile
      - name: Setup Cache
        uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-doc-${{ hashFiles('**/Cargo.lock') }}
      - run: cargo doc --all-features --no-deps

  lint-clippy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - name: Generate lockfile
        run: cargo generate-lockfile
      - name: Setup Cache
        uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-clippy-${{ hashFiles('**/Cargo.lock') }}
      - name: Select Toolchain
        uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9
        with:
          toolchain: nightly
          components: clippy
      - name: Clippy Lint
        run: cargo clippy --all --all-features -- -D warnings

  lint-rustfmt:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - name: Rustfmt
        run: cargo fmt && git diff --exit-code

  test-unit:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - name: Install cargo-llvm-cov
        uses: taiki-e/install-action@cargo-llvm-cov
      - name: Generate code coverage
        run: cargo llvm-cov --all-features --lcov --output-path lcov.info
      - name: Upload Results
        uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
        with:
          files: lcov.info


================================================
FILE: .github/workflows/cross.yml
================================================
name: cross
on:
  pull_request: {}
  push:
    branches:
      - main
env:
  CARGO_TERM_COLOR: always
jobs:
  build:
    strategy:
      fail-fast: false
      matrix:
        target:
          - x86_64-unknown-linux-gnu
          - aarch64-unknown-linux-gnu
          - powerpc64le-unknown-linux-gnu
          - s390x-unknown-linux-gnu
          - x86_64-pc-windows-gnu
          - x86_64-unknown-freebsd
          - x86_64-pc-solaris
    name: ${{matrix.target}}
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - name: Generate lockfile
        run: cargo generate-lockfile
      - name: Setup Cache
        uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cross-${{matrix.target}}-${{ hashFiles('**/Cargo.lock') }}
      - name: Install cross-rs
        run: |
          cargo install cross --git https://github.com/cross-rs/cross
          cross --version
      - name: Ensure the latest base image
        run: docker pull ghcr.io/cross-rs/${{matrix.target}}:main
      - name: Build for ${{matrix.target}}
        run: cross build -v --target ${{matrix.target}}


================================================
FILE: .github/workflows/gh-pages.yml
================================================
name: gh-pages
on:
  push:
    branches:
      - main
env:
  CARGO_TERM_COLOR: always
jobs:
  update:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - name: Generate lockfile
        run: cargo generate-lockfile
      - name: Setup Cache
        uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
      - name: Build Documentation
        run: cargo doc --all-features
      - name: Deploy Documentation
        uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0
        with:
          deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }}
          publish_branch: gh-pages
          publish_dir: ./target/doc
          force_orphan: true


================================================
FILE: .github/workflows/release.yml
================================================
name: Release

on:
  push:
    tags: [ "v[0-9]+.[0-9]+.[0-9]+*" ]

jobs:
  publish:
    name: Publish Packages
    runs-on: ubuntu-latest
    env:
      CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - name: Setup Cache
        uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
      - name: Publish
        run: cargo publish --no-verify


================================================
FILE: .github/workflows/security-audit.yml
================================================
name: Security audit
on:
  schedule:
    - cron: "0 0 * * *"
  push:
    paths:
      - "**/Cargo.toml"
      - "**/Cargo.lock"

# Declare default permissions as read only.
permissions: read-all

jobs:
  audit:
    permissions:
      checks: write # for rustsec/audit-check to create check
      contents: read # for actions/checkout to fetch code
      issues: write # for rustsec/audit-check to create issues
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - name: Generate lockfile
        run: cargo generate-lockfile
      - uses: rustsec/audit-check@69366f33c96575abad1ee0dba8212993eecbe998 # v2.0.0
        with:
          token: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .gitignore
================================================
# Generated by Cargo
# will have compiled files and executables
/target/

# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock

# These are backup files generated by rustfmt
**/*.rs.bk


================================================
FILE: .rustfmt.toml
================================================
max_width = 100
hard_tabs = false
tab_spaces = 4
newline_style = "Auto"
use_small_heuristics = "Default"
fn_call_width = 60
attr_fn_like_width = 70
struct_lit_width = 18
struct_variant_width = 35
array_width = 60
chain_width = 60
single_line_if_else_max_width = 50
reorder_imports = true
reorder_modules = true
remove_nested_parens = true
match_arm_leading_pipes = "Never"
fn_params_layout = "Tall"
edition = "2018"
merge_derives = true
use_try_shorthand = false
use_field_init_shorthand = false
force_explicit_abi = true


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Code of Conduct

We follow the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md).

Please contact one of the [project maintainers](MAINTAINERS.md) or the [CNCF
Code of Conduct Committee](mailto:conduct@cncf.io) in order to report violations
of the Code of Conduct.


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to oci-spec-rs

We'd love to have you join the community! Below summarizes the processes
that we follow.

## Topics

- [Reporting Issues](#reporting-issues)
- [Submitting Pull Requests](#submitting-pull-requests)
- [Communications](#communications)

## Reporting Issues

Before reporting an issue, check our backlog of
[open issues](https://github.com/containers/oci-spec-rs/issues)
to see if someone else has already reported it. If so, feel free to add
your scenario, or additional information, to the discussion. Or simply
"subscribe" to it to be notified when it is updated.

If you find a new issue with the project we'd love to hear about it! The most
important aspect of a bug report is that it includes enough information for
us to reproduce it. So, please include as much detail as possible and try
to remove the extra stuff that doesn't really relate to the issue itself.
The easier it is for us to reproduce it, the faster it'll be fixed!

Please don't include any private/sensitive information in your issue!

## Submitting Pull Requests

No Pull Request (PR) is too small! Typos, additional comments in the code,
new test cases, bug fixes, new features, more documentation, ... it's all
welcome!

While bug fixes can first be identified via an "issue", that is not required.
It's ok to just open up a PR with the fix, but make sure you include the same
information you would have included in an issue - like how to reproduce it.

PRs for new features should include some background on what use cases the
new code is trying to address. When possible and when it makes sense, try to break-up
larger PRs into smaller ones - it's easier to review smaller
code changes. But only if those smaller ones make sense as stand-alone PRs.

Regardless of the type of PR, all PRs should include:

- well documented code changes
- additional testcases. Ideally, they should fail w/o your code change applied
- documentation changes

Squash your commits into logical pieces of work that might want to be reviewed
separate from the rest of the PRs. But, squashing down to just one commit is ok
too since in the end the entire PR will be reviewed anyway. When in doubt,
squash.

Test your changes by running:

```console
$ make clippy
```

And you can run the test suite if you have access to elevated permissions:

```console
# cargo test
```

PRs that fix issues should include a reference like `Closes #XXXX` in the
commit message so that github will automatically close the referenced issue
when the PR is merged.

Most PRs will be reviewed by the [OWNERS](OWNERS).

### Sign your PRs

The sign-off is a line at the end of the explanation for the patch. Your
signature certifies that you wrote the patch or otherwise have the right to pass
it on as an open-source patch. The rules are simple: if you can certify
the below (from [developercertificate.org](http://developercertificate.org/)):

```
Developer Certificate of Origin
Version 1.1

Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA

Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.

Developer's Certificate of Origin 1.1

By making a contribution to this project, I certify that:

(a) The contribution was created in whole or in part by me and I
    have the right to submit it under the open source license
    indicated in the file; or

(b) The contribution is based upon previous work that, to the best
    of my knowledge, is covered under an appropriate open source
    license and I have the right under that license to submit that
    work with modifications, whether created in whole or in part
    by me, under the same open source license (unless I am
    permitted to submit under a different license), as indicated
    in the file; or

(c) The contribution was provided directly to me by some other
    person who certified (a), (b) or (c) and I have not modified
    it.

(d) I understand and agree that this project and the contribution
    are public and that a record of the contribution (including all
    personal information I submit with it, including my sign-off) is
    maintained indefinitely and may be redistributed consistent with
    this project or the open source license(s) involved.
```

Then you just add a line to every git commit message:

    Signed-off-by: John Doe <john.doe@email.com>

Use your real name (sorry, no pseudonyms or anonymous contributions.)

If you set your `user.name` and `user.email` git configs, you can sign your
commit automatically with `git commit -s`.

## Communications

For discussions around issues/bugs and features, you can use the GitHub
[issues](https://github.com/containers/oci-spec-rs/issues) and
[PRs](https://github.com/containers/oci-spec-rs/pulls) tracking system.


================================================
FILE: Cargo.toml
================================================
[package]
name = "oci-spec"
version = "0.9.0"
edition = "2021"
authors = [
    "Furisto",
    "Sascha Grunert <sgrunert@redhat.com>",
    "Toru Komatsu <k0ma@utam0k.jp>",
]
description = "Open Container Initiative Specifications in Rust"
documentation = "https://docs.rs/oci-spec"
readme = "README.md"
homepage = "https://github.com/youki-dev/oci-spec-rs"
repository = "https://github.com/youki-dev/oci-spec-rs"
license = "Apache-2.0"
keywords = ["oci", "spec", "container", "runtime", "image"]
categories = ["api-bindings"]

[features]
default = ["distribution", "image", "runtime"]
proptests = ["quickcheck"]
distribution = []
image = []
runtime = []

[dependencies]
const_format = "0.2"
serde = { version = "1.0.129", features = ["derive"] }
thiserror = "2.0.0"
serde_json = "1.0.66"
quickcheck = { version = "1.0.3", optional = true }
derive_builder = "0.20.0"
getset = "0.1.3"
strum = "0.27.0"
strum_macros = "0.27.0"
regex = "1"

[dev-dependencies]
tempfile = "3.23.0"
rstest = "0.26.1"
serde_json = { version = "1.0.66", features = ["preserve_order"] }


================================================
FILE: GOVERNANCE.md
================================================
# oci-spec-rs Project Governance

The oci-spec-rs project is dedicated to creating an OCI Runtime, Image and
Distribution specification in Rust. This governance explains how the project is
run.

- [Values](#values)
- [Maintainers](#maintainers)
- [Becoming a Maintainer](#becoming-a-maintainer)
- [Meetings](#meetings)
- [CNCF Resources](#cncf-resources)
- [Code of Conduct Enforcement](#code-of-conduct)
- [Security Response Team](#security-response-team)
- [Voting](#voting)
- [Modifications](#modifying-this-charter)

## Values

The oci-spec-rs project and its leadership embrace the following values:

- Openness: Communication and decision-making happens in the open and is
  discoverable for future reference. As much as possible, all discussions and
  work take place in public forums and open repositories.

- Fairness: All stakeholders have the opportunity to provide feedback and submit
  contributions, which will be considered on their merits.

- Community over Product or Company: Sustaining and growing our community takes
  priority over shipping code or sponsors' organizational goals. Each
  contributor participates in the project as an individual.

- Inclusivity: We innovate through different perspectives and skill sets, which
  can only be accomplished in a welcoming and respectful environment.

- Participation: Responsibilities within the project are earned through
  participation, and there is a clear path up the contributor ladder into
  leadership positions.

## Maintainers

oci-spec-rs Maintainers have write access to the project GitHub repository.
They can merge their own patches or patches from others. The current maintainers
can be found in [MAINTAINERS.md](MAINTAINERS.md). Maintainers collectively
manage the project's resources and contributors.

This privilege is granted with some expectation of responsibility: maintainers
are people who care about the oci-spec-rs project and want to help it grow and
improve. A maintainer is not just someone who can make changes, but someone who
has demonstrated their ability to collaborate with the team, get the most
knowledgeable people to review code and docs, contribute high-quality code, and
follow through to fix issues (in code or tests).

A maintainer is a contributor to the project's success and a citizen helping the
project succeed.

The collective team of all Maintainers is known as the Maintainer Council, which
is the governing body for the project.

### Becoming a Maintainer

To become a Maintainer you need to demonstrate the following:

- commitment to the project:
  - participate in discussions, contributions, code and documentation reviews
    for 3 months or more,
  - perform reviews for at least 10 non-trivial pull requests,
  - contribute at least 5 non-trivial pull requests and have them merged,
- ability to write quality code and/or documentation,
- ability to collaborate with the team,
- understanding of how the team works (policies, processes for testing and code
  review, etc),
- understanding of the project's code base and coding and documentation style.

A new Maintainer must be proposed by an existing maintainer by opening an issue
within this repository. A simple majority vote of existing Maintainers approves
the application. Maintainers nominations will be evaluated without prejudice to
employer or demographics.

Maintainers who are selected will be granted the necessary GitHub rights.

### Removing a Maintainer

Maintainers may resign at any time if they feel that they will not be able to
continue fulfilling their project duties.

Maintainers may also be removed after being inactive, failure to fulfill their
Maintainer responsibilities, violating the Code of Conduct, or other reasons.
Inactivity is defined as a period of very low or no activity in the project
for a year or more, with no definite schedule to return to full Maintainer
activity.

A Maintainer may be removed at any time by a 2/3 vote of the remaining
maintainers.

Depending on the reason for removal, a Maintainer may be converted to Emeritus
status. Emeritus Maintainers will still be consulted on some project matters,
and can be rapidly returned to Maintainer status if their availability changes.

## Meetings

There are no public meetings planned for this particular project.

Maintainers may have closed meetings in order to discuss security reports or
Code of Conduct violations. Such meetings should be scheduled by any Maintainer
on receipt of a security issue or CoC report. All current Maintainers must be
invited to such closed meetings, except for any Maintainer who is accused of a
CoC violation.

## CNCF Resources

Any Maintainer may suggest a request for CNCF resources, either as issue or
discussion within this repository or during a meeting. A simple majority of
Maintainers approves the request. The Maintainers may also choose to delegate
working with the CNCF to non-Maintainer community members, who will then be
added to the [CNCF's Maintainer
List](https://github.com/cncf/foundation/blob/main/project-maintainers.csv)
for that purpose.

## Code of Conduct

[Code of Conduct](CODE_OF_CONDUCT.md) violations by community members will be
discussed and resolved by the Maintainers privately. If a Maintainer is directly
involved in the report, the Maintainers will instead designate two Maintainers
to work with the CNCF Code of Conduct Committee in resolving it.

## Security Response Team

The Maintainers will appoint a Security Response Team to handle security
reports. This committee may simply consist of the Maintainer Council themselves.
If this responsibility is delegated, the Maintainers will appoint a team of at
least two contributors to handle it. The Maintainers will review who is assigned
to this at least once a year.

The Security Response Team is responsible for handling all reports of security
holes and breaches according to the [security policy](SECURITY.md).

## Voting

While most business in oci-spec-rs is conducted by "[lazy
consensus](https://community.apache.org/committers/lazyConsensus.html)",
periodically the Maintainers may need to vote on specific actions or changes. A
vote can be taken on an GitHub issue or discussion within the project.

Most votes require a simple majority of all Maintainers to succeed, except where
otherwise noted. Two-thirds majority votes mean at least two-thirds of all
existing maintainers.

## Modifying this Charter

Changes to this Governance and its supporting documents may be approved by a 2/3
vote of the Maintainers.


================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [2021] [youki team]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: MAINTAINERS.md
================================================
The current Maintainers Group for the oci-spec-rs project consists of:

| Name            | Employer           | GitHub handle    | Responsibilities        |
| --------------- | ------------------ | ---------------- | ----------------------- |
| Colin Walters   | Red Hat            | @cgwalters       | Approver and Maintainer |
| Flavio Castelli | SUSE               | @flavio          | Approver and Maintainer |
| Sascha Grunert  | Red Hat            | @saschagrunert   | Approver and Maintainer |
| Taylor Thomas   | Cosmonic           | @thomastaylor312 | Approver and Maintainer |
| Toru Komatsu    | Preferred Networks | @utam0k          | Approver and Maintainer |
| Eric Fang       | Independent        | @yihuaf          | Maintainer              |
| Jorge Prendes   | Microsoft          | @jprendes        | Maintainer              |
| Thomas Schubart | Gitpod             | @Furisto         | Maintainer              |
| Yashodhan       | Independent        | @YJDoc2          | Maintainer              |

This list must be kept in sync with the [CNCF Project Maintainers list](https://github.com/cncf/foundation/blob/master/project-maintainers.csv).

See [the project Governance](GOVERNANCE.md) for how maintainers are selected and replaced.


================================================
FILE: OWNERS
================================================
filters:
  .*:
    reviewers:
    - oci-spec-rs-reviewers
    approvers:
    - oci-spec-rs-approvers
    - youki-maintainers


================================================
FILE: OWNERS_ALIASES
================================================
aliases:
  oci-spec-rs-approvers:
    - cgwalters
    - flavio
    - saschagrunert
    - thomastaylor312
    - utam0k
  youki-maintainers:
    - Furisto
    - jprendes
    - yihuaf
    - YJDoc2
  oci-spec-rs-reviewers: []


================================================
FILE: README.md
================================================
# oci-spec-rs

[![ci](https://github.com/containers/oci-spec-rs/workflows/ci/badge.svg)](https://github.com/containers/oci-spec-rs/actions)
[![gh-pages](https://github.com/containers/oci-spec-rs/workflows/gh-pages/badge.svg)](https://github.com/containers/oci-spec-rs/actions)
[![crates.io](https://img.shields.io/crates/v/oci-spec.svg)](https://crates.io/crates/oci-spec)
[![codecov](https://codecov.io/gh/containers/oci-spec-rs/branch/main/graph/badge.svg)](https://codecov.io/gh/containers/oci-spec-rs)
[![docs](https://img.shields.io/badge/docs-main-blue.svg)](https://containers.github.io/oci-spec-rs/oci_spec/index.html)
[![docs.rs](https://docs.rs/oci-spec/badge.svg)](https://docs.rs/oci-spec)
[![dependencies](https://deps.rs/repo/github/containers/oci-spec-rs/status.svg)](https://deps.rs/repo/github/containers/oci-spec-rs)
[![license](https://img.shields.io/github/license/containers/oci-spec-rs.svg)](https://github.com/containers/oci-spec-rs/blob/master/LICENSE)
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fyouki-dev%2Foci-spec-rs.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fyouki-dev%2Foci-spec-rs?ref=badge_shield)

### Open Container Initiative (OCI) Specifications for Rust

This library provides a convenient way to interact with the specifications defined by the [Open Container Initiative (OCI)](https://opencontainers.org). 

- [Image Format Specification](https://github.com/opencontainers/image-spec/blob/main/spec.md)
- [Runtime Specification](https://github.com/opencontainers/runtime-spec/blob/master/spec.md)
- [Distribution Specification](https://github.com/opencontainers/distribution-spec/blob/main/spec.md)

```toml
[dependencies]
oci-spec = "0.9.0"
```
*Compiler support: requires rustc 1.54+*

If you want to propose or cut a new release, then please follow our 
[release process documentation](./release.md).

## Image Format Spec Examples
- Load image manifest from filesystem
```rust no_run
use oci_spec::image::ImageManifest;

let image_manifest = ImageManifest::from_file("manifest.json").unwrap();
assert_eq!(image_manifest.layers().len(), 5);
```

- Create new image manifest using builder
```rust no_run
use std::str::FromStr;
use oci_spec::image::{
    Descriptor, 
    DescriptorBuilder, 
    ImageManifest, 
    ImageManifestBuilder, 
    MediaType, 
    Sha256Digest,
    SCHEMA_VERSION
};

let config = DescriptorBuilder::default()
            .media_type(MediaType::ImageConfig)
            .size(7023u64)
            .digest(Sha256Digest::from_str("b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7").unwrap())
            .build()
            .expect("build config descriptor");

let layers: Vec<Descriptor> = [
    (
        32654u64,
        "9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0",
    ),
    (
        16724,
        "3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b",
    ),
    (
        73109,
        "ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736",
    ),
]
    .iter()
    .map(|l| {
    DescriptorBuilder::default()
        .media_type(MediaType::ImageLayerGzip)
        .size(l.0)
        .digest(Sha256Digest::from_str(l.1).unwrap())
        .build()
        .expect("build layer")
    })
    .collect();

let image_manifest = ImageManifestBuilder::default()
    .schema_version(SCHEMA_VERSION)
    .config(config)
    .layers(layers)
    .build()
    .expect("build image manifest");

image_manifest.to_file_pretty("my-manifest.json").unwrap();
```

- Content of my-manifest.json
```json
{
  "schemaVersion": 2,
  "config": {
    "mediaType": "application/vnd.oci.image.config.v1+json",
    "digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7",
    "size": 7023
  },
  "layers": [
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "digest": "sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0",
      "size": 32654
    },
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "digest": "sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b",
      "size": 16724
    },
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "digest": "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736",
      "size": 73109
    }
  ]
}
```

## Distribution Spec Examples
- Create a list of repositories 
```rust
use oci_spec::distribution::RepositoryListBuilder;

let list = RepositoryListBuilder::default()
            .repositories(vec!["busybox".to_owned()])
            .build().unwrap();
```

# Contributing
This project welcomes your PRs and issues. Should you wish to work on an issue, please claim it first by commenting on the 
issue that you want to work on it. This is to prevent duplicated efforts from contributors on the same issue.


## License
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fyouki-dev%2Foci-spec-rs.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fyouki-dev%2Foci-spec-rs?ref=badge_large)

================================================
FILE: SECURITY.md
================================================
# oci-spec-rs Security

Security is taken seriously and has high priority across all related projects to
ensure users can trust this project for their systems.

We're extremely grateful for security researchers and users that report
vulnerabilities to the community. All reports are thoroughly investigated by a
set of community volunteers.

## Report a Vulnerability

<!-- TODO: the mailing list has to be requested -->

To make a report, email the vulnerability to the private
[cncf-oci-spec-rs-security@lists.cncf.io](mailto:cncf-crio-security@lists.cncf.io) list
with the security details.

You can expect an initial response to the report within 3 business days.
Possible fixes for vulnerabilities will be then discussed via the mail thread
and can be considered as automatically embargoed until they got merged into all
related branches. A project approver or reviewer (as defined in the
[OWNERS](./OWNERS) file) will coordinate how the pull requests and patches are
being incorporated into the repository without breaking the embargo.

### When Should I Report a Vulnerability?

- You think you discovered a potential security vulnerability
- You are unsure how a vulnerability affects this project
- You think you discovered a vulnerability in another project that oci-spec-rs
  depends on (for projects with their own vulnerability reporting and disclosure
  process, please report it directly there)

### When Should I NOT Report a Vulnerability?

- You need help tuning components for security
- You need help applying security related updates
- Your issue is not security related


================================================
FILE: SECURITY_CONTACTS
================================================
# Defined below are the security contacts for this repo.
#
# They are the contact point for the Product Security Team to reach out
# to for triaging and handling of incoming issues.
#
# The below names agree to abide by the
# [Embargo Policy](https://git.k8s.io/security/private-distributors-list.md#embargo-policy)
# and will be removed and replaced if they violate that agreement.
#
# DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE
# INSTRUCTIONS AT ./SECURITY.md

cgwalters
flavio
saschagrunert
thomastaylor312
utam0k


================================================
FILE: hack/release
================================================
#!/usr/bin/env bash
set -euo pipefail

VERSION=${1:-}

if [[ -z "$VERSION" ]]; then
    echo "Please provide a version to be released"
    exit 1
fi

git checkout -b "release-$VERSION"
sed -Ei "s/^(version = ).*/\1\"$VERSION\"/g" Cargo.toml
sed -Ei "s/^(oci-spec = ).*/\1\"$VERSION\"/g" README.md

VERSION_DEV_FILES=(
    src/distribution/version.rs
    src/image/version.rs
    src/runtime/version.rs
)

for FILE in "${VERSION_DEV_FILES[@]}"; do
    sed -Ei "s/^(pub const VERSION_DEV: &str = ).*/\1\"\";/g" "$FILE"
    sed -Ei 's/(assert_eq!\(version\(\), "[0-9]+\.[0-9]+\.[0-9]+).*("\.to_string\(\)\))/\1\2/g' "$FILE"
done
git add .
git commit -sm "Bump to $VERSION"

for FILE in "${VERSION_DEV_FILES[@]}"; do
    sed -Ei "s/^(pub const VERSION_DEV: &str = ).*/\1\"-dev\";/g" "$FILE"
    sed -Ei 's/(assert_eq!\(version\(\), "[0-9]+\.[0-9]+\.[0-9]+).*("\.to_string\(\)\))/\1-dev\2/g' "$FILE"
done
git add .
git commit -sm "Back to dev"


================================================
FILE: release.md
================================================
# Releasing a new version of oci-spec-rs

A new release of this crate can be proposed by running the version bump script:

```console
./hack/release x.y.z
```

Push the changes to your fork and draft a new GitHub Pull Request (PR) which
should now contain 2 commits, one which bumps the release version and another
one to turn it _back to dev_.

If the PR got merged, then create a new tag pointing to the first commit of that
PR (named _Bump to x.y.z_). The changelog can be created by using GitHub's
release note creation feature via the user interface.


================================================
FILE: src/distribution/error.rs
================================================
//! Error types of the distribution spec.

use crate::error::OciSpecError;
use derive_builder::Builder;
use getset::Getters;
use serde::{Deserialize, Serialize};
use std::fmt::{self, Display, Formatter};
use strum_macros::{Display as StrumDisplay, EnumString};
use thiserror::Error;

/// The string returned by and ErrorResponse error.
pub const ERR_REGISTRY: &str = "distribution: registry returned error";

/// Unique identifier representing error code.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, StrumDisplay, EnumString)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ErrorCode {
    /// Blob unknown to registry.
    BlobUnknown,
    /// Blob upload invalid.
    BlobUploadInvalid,
    /// Blob upload unknown to registry.
    BlobUploadUnknown,
    /// Provided digest did not match uploaded content.
    DigestInvalid,
    /// Blob unknown to registry.
    ManifestBlobUnknown,
    /// Manifest invalid.
    ManifestInvalid,
    /// Manifest unknown.
    ManifestUnknown,
    /// Invalid repository name.
    NameInvalid,
    /// Repository name not known to registry.
    NameUnknown,
    /// Provided length did not match content length.
    SizeInvalid,
    /// Authentication required.
    Unauthorized,
    /// Requested access to the resource is denied.
    Denied,
    /// The operation is unsupported.
    Unsupported,
    /// Too many requests.
    #[serde(rename = "TOOMANYREQUESTS")]
    TooManyRequests,
}

#[derive(Builder, Clone, Debug, Deserialize, Eq, Error, Getters, PartialEq, Serialize)]
#[builder(
    pattern = "owned",
    setter(into, strip_option),
    build_fn(error = "OciSpecError")
)]
#[getset(get = "pub")]
/// ErrorResponse is returned by a registry on an invalid request.
pub struct ErrorResponse {
    /// Available errors within the response.
    errors: Vec<ErrorInfo>,
}

impl Display for ErrorResponse {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "{ERR_REGISTRY}")
    }
}

impl ErrorResponse {
    /// Returns the ErrorInfo slice for the response.
    pub fn detail(&self) -> &[ErrorInfo] {
        &self.errors
    }
}

#[derive(Builder, Clone, Debug, Deserialize, Eq, Getters, PartialEq, Serialize)]
#[builder(
    pattern = "owned",
    setter(into, strip_option),
    build_fn(error = "OciSpecError")
)]
#[getset(get = "pub")]
/// Describes a server error returned from a registry.
pub struct ErrorInfo {
    /// The code field MUST be a unique identifier, containing only uppercase alphabetic
    /// characters and underscores.
    code: ErrorCode,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    #[builder(default = "None")]
    /// The message field is OPTIONAL, and if present, it SHOULD be a human readable string or
    /// MAY be empty.
    message: Option<String>,

    #[serde(default, skip_serializing_if = "Option::is_none", with = "json_string")]
    #[builder(default = "None")]
    /// The detail field is OPTIONAL and MAY contain arbitrary JSON data providing information
    /// the client can use to resolve the issue.
    detail: Option<String>,
}

mod json_string {
    use std::str::FromStr;

    use serde::{Deserialize, Deserializer, Serialize, Serializer};

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
    where
        D: Deserializer<'de>,
    {
        use serde::de::Error;

        let opt = Option::<serde_json::Value>::deserialize(deserializer)?;

        if let Some(data) = opt {
            let data = serde_json::to_string(&data).map_err(Error::custom)?;
            return Ok(Some(data));
        }

        Ok(None)
    }

    pub fn serialize<S>(target: &Option<String>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        use serde::ser::Error;

        match target {
            Some(data) => {
                if let Ok(json_value) = serde_json::Value::from_str(data) {
                    json_value.serialize(serializer)
                } else {
                    Err(Error::custom("invalid JSON"))
                }
            }
            _ => unreachable!(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::error::Result;

    #[test]
    fn error_response_success() -> Result<()> {
        let response = ErrorResponseBuilder::default().errors(vec![]).build()?;
        assert!(response.detail().is_empty());
        assert_eq!(response.to_string(), ERR_REGISTRY);
        Ok(())
    }

    #[test]
    fn error_response_failure() {
        assert!(ErrorResponseBuilder::default().build().is_err());
    }

    #[test]
    fn error_info_success() -> Result<()> {
        let info = ErrorInfoBuilder::default()
            .code(ErrorCode::BlobUnknown)
            .build()?;
        assert_eq!(info.code(), &ErrorCode::BlobUnknown);
        assert!(info.message().is_none());
        assert!(info.detail().is_none());
        Ok(())
    }

    #[test]
    fn error_info_failure() {
        assert!(ErrorInfoBuilder::default().build().is_err());
    }

    #[test]
    fn error_info_serialize_success() -> Result<()> {
        let error_info = ErrorInfoBuilder::default()
            .code(ErrorCode::Unauthorized)
            .detail(String::from("{ \"key\": \"value\" }"))
            .build()?;

        assert!(serde_json::to_string(&error_info).is_ok());
        Ok(())
    }

    #[test]
    fn error_info_serialize_failure() -> Result<()> {
        let error_info = ErrorInfoBuilder::default()
            .code(ErrorCode::Unauthorized)
            .detail(String::from("abcd"))
            .build()?;

        assert!(serde_json::to_string(&error_info).is_err());
        Ok(())
    }

    #[test]
    fn error_info_deserialize_success() -> Result<()> {
        let error_info_str = r#"
        {
            "code": "MANIFEST_UNKNOWN",
            "message": "manifest tagged by \"lates\" is not found",
            "detail": {
                "Tag": "lates"
            }
        }"#;

        let error_info: ErrorInfo = serde_json::from_str(error_info_str)?;
        assert_eq!(error_info.detail().as_ref().unwrap(), "{\"Tag\":\"lates\"}");

        Ok(())
    }
}


================================================
FILE: src/distribution/mod.rs
================================================
//! [OCI distribution spec](https://github.com/opencontainers/distribution-spec) types and definitions.
//!
//! The Open Container Initiative Distribution Specification (a.k.a. "OCI Distribution Spec")
//! defines an API protocol to facilitate and standardize the distribution of content.
//!
//! While OCI Image is the most prominent, the specification is designed to be agnostic of content
//! types. Concepts such as "manifests" and "digests", are currently defined in the [Open Container
//! Initiative Image Format Specification](https://github.com/opencontainers/image-spec) (a.k.a.
//! "OCI Image Spec").
//!
//! To support other artifact types, please see the [Open Container Initiative Artifact Authors
//! Guide](https://github.com/opencontainers/artifacts) (a.k.a. "OCI Artifacts").

mod error;
mod reference;
mod repository;
mod tag;
mod version;

pub use error::*;
pub use reference::*;
pub use repository::*;
pub use tag::*;
pub use version::*;


================================================
FILE: src/distribution/reference.rs
================================================
use std::fmt;
use std::str::FromStr;
use std::{convert::TryFrom, sync::OnceLock};

use regex::{Regex, RegexBuilder};
use serde::{Deserialize, Serialize};
use thiserror::Error;

/// NAME_TOTAL_LENGTH_MAX is the maximum total number of characters in a repository name.
const NAME_TOTAL_LENGTH_MAX: usize = 255;

const DOCKER_HUB_DOMAIN_LEGACY: &str = "index.docker.io";
const DOCKER_HUB_DOMAIN: &str = "docker.io";
const DOCKER_HUB_OFFICIAL_REPO_NAME: &str = "library";
const DEFAULT_TAG: &str = "latest";
/// REFERENCE_REGEXP is the full supported format of a reference. The regexp
/// is anchored and has capturing groups for name, tag, and digest components.
const REFERENCE_REGEXP: &str = r"^((?:(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])(?:(?:\.(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(?::[0-9]+)?/)?[a-z0-9]+(?:(?:(?:[._]|__|[-]*)[a-z0-9]+)+)?(?:(?:/[a-z0-9]+(?:(?:(?:[._]|__|[-]*)[a-z0-9]+)+)?)+)?)(?::([\w][\w.-]{0,127}))?(?:@([A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}))?$";

fn reference_regexp() -> &'static Regex {
    static RE: OnceLock<Regex> = OnceLock::new();
    RE.get_or_init(|| {
        RegexBuilder::new(REFERENCE_REGEXP)
            .size_limit(10 * (1 << 21))
            .build()
            .unwrap()
    })
}

/// Reasons that parsing a string as a Reference can fail.
#[derive(Debug, Error, PartialEq, Eq)]
pub enum ParseError {
    /// Will be returned if digest is ill-formed
    #[error("invalid checksum digest format")]
    DigestInvalidFormat,
    /// Will be returned if digest does not have a correct length
    #[error("invalid checksum digest length")]
    DigestInvalidLength,
    /// Will be returned for an unknown digest algorithm
    #[error("unsupported digest algorithm")]
    DigestUnsupported,
    /// Will be returned for an uppercase character in repository name
    #[error("repository name must be lowercase")]
    NameContainsUppercase,
    /// Will be returned if a name is empty
    #[error("repository name must have at least one component")]
    NameEmpty,
    /// Will be returned if a name is too long
    #[error("repository name must not be more than {NAME_TOTAL_LENGTH_MAX} characters")]
    NameTooLong,
    /// Will be returned if a reference is ill-formed
    #[error("invalid reference format")]
    ReferenceInvalidFormat,
    /// Will be returned if a tag is ill-formed
    #[error("invalid tag format")]
    TagInvalidFormat,
}

/// Reference provides a general type to represent any way of referencing images within an OCI registry.
///
/// # Examples
///
/// Parsing a tagged image reference:
///
/// ```
/// use oci_spec::distribution::Reference;
///
/// let reference: Reference = "docker.io/library/hello-world:latest".parse().unwrap();
///
/// assert_eq!("docker.io/library/hello-world:latest", reference.whole().as_str());
/// assert_eq!("docker.io", reference.registry());
/// assert_eq!("library/hello-world", reference.repository());
/// assert_eq!(Some("latest"), reference.tag());
/// assert_eq!(None, reference.digest());
/// ```
#[derive(Clone, Hash, PartialEq, Eq, Debug, Serialize, Deserialize)]
pub struct Reference {
    registry: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    mirror_registry: Option<String>,
    repository: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    tag: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    digest: Option<String>,
}

impl Reference {
    /// Create a Reference with a registry, repository and tag.
    pub fn with_tag(registry: String, repository: String, tag: String) -> Self {
        Self {
            registry,
            mirror_registry: None,
            repository,
            tag: Some(tag),
            digest: None,
        }
    }

    /// Create a Reference with a registry, repository and digest.
    pub fn with_digest(registry: String, repository: String, digest: String) -> Self {
        Self {
            registry,
            mirror_registry: None,
            repository,
            tag: None,
            digest: Some(digest),
        }
    }

    /// Create a new instance of [`Reference`] with a registry, repository, tag and digest.
    ///
    /// This is useful when you need to reference an image by both its semantic version (tag)
    /// and its content-addressable digest for immutability.
    ///
    /// # Examples
    ///
    /// ```
    /// use oci_spec::distribution::Reference;
    ///
    /// let reference = Reference::with_tag_and_digest(
    ///     "docker.io".to_string(),
    ///     "library/nginx".to_string(),
    ///     "1.21".to_string(),
    ///     "sha256:abc123...".to_string(),
    /// );
    /// ```
    pub fn with_tag_and_digest(
        registry: String,
        repository: String,
        tag: String,
        digest: String,
    ) -> Self {
        Self {
            registry,
            mirror_registry: None,
            repository,
            tag: Some(tag),
            digest: Some(digest),
        }
    }

    /// Clone the Reference for the same image with a new digest.
    pub fn clone_with_digest(&self, digest: String) -> Self {
        Self {
            registry: self.registry.clone(),
            mirror_registry: self.mirror_registry.clone(),
            repository: self.repository.clone(),
            tag: None,
            digest: Some(digest),
        }
    }

    /// Set a pull mirror registry for this reference.
    ///
    /// The mirror registry will be used to resolve the image, the original registry
    /// is available via the [`Reference::namespace`] function.
    ///
    /// The original registry will be sent with the `ns` query parameter to the mirror registry.
    /// The `ns` query parameter is currently not part of the stable OCI Distribution Spec yet,
    /// but is being discussed to be added and is already used by some other implementations
    /// (for example containerd). So be aware that this feature might not work with all registries.
    ///
    /// Since this is not part of the stable OCI Distribution Spec yet, this feature is exempt from
    /// semver backwards compatibility guarantees and might change in the future.
    #[doc(hidden)]
    pub fn set_mirror_registry(&mut self, registry: String) {
        self.mirror_registry = Some(registry);
    }

    /// Resolve the registry address of a given `Reference`.
    ///
    /// Some registries, such as docker.io, uses a different address for the actual
    /// registry. This function implements such redirection.
    ///
    /// If a mirror registry is set, it will be used instead of the original registry.
    pub fn resolve_registry(&self) -> &str {
        match (self.registry(), self.mirror_registry.as_deref()) {
            (_, Some(mirror_registry)) => mirror_registry,
            ("docker.io", None) => "index.docker.io",
            (registry, None) => registry,
        }
    }

    /// Returns the name of the registry.
    pub fn registry(&self) -> &str {
        &self.registry
    }

    /// Returns the name of the repository.
    pub fn repository(&self) -> &str {
        &self.repository
    }

    /// Returns the object's tag, if present.
    pub fn tag(&self) -> Option<&str> {
        self.tag.as_deref()
    }

    /// Returns the object's digest, if present.
    pub fn digest(&self) -> Option<&str> {
        self.digest.as_deref()
    }

    /// Returns the original registry when pulled via a mirror.
    ///
    /// Since this is not part of the stable OCI Distribution Spec yet, this feature is exempt from
    /// semver backwards compatibility guarantees and might change in the future.
    #[doc(hidden)]
    pub fn namespace(&self) -> Option<&str> {
        if self.mirror_registry.is_some() {
            Some(self.registry())
        } else {
            None
        }
    }

    /// Returns the whole reference.
    pub fn whole(&self) -> String {
        self.to_string()
    }
}

impl fmt::Display for Reference {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut not_empty = false;
        if !self.registry().is_empty() {
            write!(f, "{}", self.registry())?;
            not_empty = true;
        }
        if !self.repository().is_empty() {
            if not_empty {
                write!(f, "/")?;
            }
            write!(f, "{}", self.repository())?;
            not_empty = true;
        }
        if let Some(t) = self.tag() {
            if not_empty {
                write!(f, ":")?;
            }
            write!(f, "{t}")?;
            not_empty = true;
        }
        if let Some(d) = self.digest() {
            if not_empty {
                write!(f, "@")?;
            }
            write!(f, "{d}")?;
        }
        Ok(())
    }
}

impl FromStr for Reference {
    type Err = ParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Reference::try_from(s)
    }
}

impl TryFrom<&str> for Reference {
    type Error = ParseError;

    fn try_from(s: &str) -> Result<Self, Self::Error> {
        if s.is_empty() {
            return Err(ParseError::NameEmpty);
        }
        let captures = match reference_regexp().captures(s) {
            Some(caps) => caps,
            None => {
                return Err(ParseError::ReferenceInvalidFormat);
            }
        };
        let name = &captures[1];
        let mut tag = captures.get(2).map(|m| m.as_str().to_owned());
        let digest = captures.get(3).map(|m| m.as_str().to_owned());
        if tag.is_none() && digest.is_none() {
            tag = Some(DEFAULT_TAG.into());
        }
        let (registry, repository) = split_domain(name);
        let reference = Reference {
            registry,
            mirror_registry: None,
            repository,
            tag,
            digest,
        };
        if reference.repository().len() > NAME_TOTAL_LENGTH_MAX {
            return Err(ParseError::NameTooLong);
        }
        // Digests much always be hex-encoded, ensuring that their hex portion will always be
        // size*2
        if let Some(digest) = reference.digest() {
            match digest.split_once(':') {
                None => return Err(ParseError::DigestInvalidFormat),
                Some(("sha256", digest)) => {
                    if digest.len() != 64 {
                        return Err(ParseError::DigestInvalidLength);
                    }
                }
                Some(("sha384", digest)) => {
                    if digest.len() != 96 {
                        return Err(ParseError::DigestInvalidLength);
                    }
                }
                Some(("sha512", digest)) => {
                    if digest.len() != 128 {
                        return Err(ParseError::DigestInvalidLength);
                    }
                }
                Some((_, _)) => return Err(ParseError::DigestUnsupported),
            }
        }
        Ok(reference)
    }
}

impl TryFrom<String> for Reference {
    type Error = ParseError;
    fn try_from(string: String) -> Result<Self, Self::Error> {
        TryFrom::try_from(string.as_str())
    }
}

impl From<Reference> for String {
    fn from(reference: Reference) -> Self {
        reference.whole()
    }
}

/// Splits a repository name to domain and remotename string.
/// If no valid domain is found, the default domain is used. Repository name
/// needs to be already validated before.
///
/// This function is a Rust rewrite of the official Go code used by Docker:
/// https://github.com/distribution/distribution/blob/41a0452eea12416aaf01bceb02a924871e964c67/reference/normalize.go#L87-L104
fn split_domain(name: &str) -> (String, String) {
    let mut domain: String;
    let mut remainder: String;

    match name.split_once('/') {
        None => {
            domain = DOCKER_HUB_DOMAIN.into();
            remainder = name.into();
        }
        Some((left, right)) => {
            if !(left.contains('.') || left.contains(':')) && left != "localhost" {
                domain = DOCKER_HUB_DOMAIN.into();
                remainder = name.into();
            } else {
                domain = left.into();
                remainder = right.into();
            }
        }
    }
    if domain == DOCKER_HUB_DOMAIN_LEGACY {
        domain = DOCKER_HUB_DOMAIN.into();
    }
    if domain == DOCKER_HUB_DOMAIN && !remainder.contains('/') {
        remainder = format!("{DOCKER_HUB_OFFICIAL_REPO_NAME}/{remainder}");
    }

    (domain, remainder)
}

#[cfg(test)]
mod test {
    use super::*;

    mod parse {
        use super::*;
        use rstest::rstest;

        #[rstest(input, registry, repository, tag, digest, whole,
            case("busybox", "docker.io", "library/busybox", Some("latest"), None, "docker.io/library/busybox:latest"),
            case("test.com:tag", "docker.io", "library/test.com", Some("tag"), None, "docker.io/library/test.com:tag"),
            case("test.com:5000", "docker.io", "library/test.com", Some("5000"), None, "docker.io/library/test.com:5000"),
            case("test.com/repo:tag", "test.com", "repo", Some("tag"), None, "test.com/repo:tag"),
            case("test:5000/repo", "test:5000", "repo", Some("latest"), None, "test:5000/repo:latest"),
            case("test:5000/repo:tag", "test:5000", "repo", Some("tag"), None, "test:5000/repo:tag"),
            case("test:5000/repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "test:5000", "repo", None, Some("sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), "test:5000/repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
            case("test:5000/repo:tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "test:5000", "repo", Some("tag"), Some("sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), "test:5000/repo:tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
            case("lowercase:Uppercase", "docker.io", "library/lowercase", Some("Uppercase"), None, "docker.io/library/lowercase:Uppercase"),
            case("sub-dom1.foo.com/bar/baz/quux", "sub-dom1.foo.com", "bar/baz/quux", Some("latest"), None, "sub-dom1.foo.com/bar/baz/quux:latest"),
            case("sub-dom1.foo.com/bar/baz/quux:some-long-tag", "sub-dom1.foo.com", "bar/baz/quux", Some("some-long-tag"), None, "sub-dom1.foo.com/bar/baz/quux:some-long-tag"),
            case("b.gcr.io/test.example.com/my-app:test.example.com", "b.gcr.io", "test.example.com/my-app", Some("test.example.com"), None, "b.gcr.io/test.example.com/my-app:test.example.com"),
            // ☃.com in punycode
            case("xn--n3h.com/myimage:xn--n3h.com", "xn--n3h.com", "myimage", Some("xn--n3h.com"), None, "xn--n3h.com/myimage:xn--n3h.com"),
            // 🐳.com in punycode
            case("xn--7o8h.com/myimage:xn--7o8h.com@sha512:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "xn--7o8h.com", "myimage", Some("xn--7o8h.com"), Some("sha512:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), "xn--7o8h.com/myimage:xn--7o8h.com@sha512:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
            case("foo_bar.com:8080", "docker.io", "library/foo_bar.com", Some("8080"), None, "docker.io/library/foo_bar.com:8080" ),
            case("foo/foo_bar.com:8080", "docker.io", "foo/foo_bar.com", Some("8080"), None, "docker.io/foo/foo_bar.com:8080"),
            case("opensuse/leap:15.3", "docker.io", "opensuse/leap", Some("15.3"), None, "docker.io/opensuse/leap:15.3"),
        )]
        fn parse_good_reference(
            input: &str,
            registry: &str,
            repository: &str,
            tag: Option<&str>,
            digest: Option<&str>,
            whole: &str,
        ) {
            println!("input: {}", input);
            let reference = Reference::try_from(input).expect("could not parse reference");
            println!("{} -> {:?}", input, reference);
            assert_eq!(registry, reference.registry());
            assert_eq!(repository, reference.repository());
            assert_eq!(tag, reference.tag());
            assert_eq!(digest, reference.digest());
            assert_eq!(whole, reference.whole());
        }

        #[rstest(input, err,
            case("", ParseError::NameEmpty),
            case(":justtag", ParseError::ReferenceInvalidFormat),
            case("@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", ParseError::ReferenceInvalidFormat),
            case("repo@sha256:ffffffffffffffffffffffffffffffffff", ParseError::DigestInvalidLength),
            case("validname@invaliddigest:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", ParseError::DigestUnsupported),
            // FIXME: should really pass a ParseError::NameContainsUppercase, but "invalid format" is good enough for now.
            case("Uppercase:tag", ParseError::ReferenceInvalidFormat),
            // FIXME: "Uppercase" is incorrectly handled as a domain-name here, and therefore passes.
            // https://github.com/docker/distribution/blob/master/reference/reference_test.go#L104-L109
            // case("Uppercase/lowercase:tag", ParseError::NameContainsUppercase),
            // FIXME: should really pass a ParseError::NameContainsUppercase, but "invalid format" is good enough for now.
            case("test:5000/Uppercase/lowercase:tag", ParseError::ReferenceInvalidFormat),
            case("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", ParseError::NameTooLong),
            case("aa/asdf$$^/aa", ParseError::ReferenceInvalidFormat)
        )]
        fn parse_bad_reference(input: &str, err: ParseError) {
            assert_eq!(Reference::try_from(input).unwrap_err(), err)
        }

        #[rstest(
            input,
            registry,
            resolved_registry,
            whole,
            case(
                "busybox",
                "docker.io",
                "index.docker.io",
                "docker.io/library/busybox:latest"
            ),
            case("test.com/repo:tag", "test.com", "test.com", "test.com/repo:tag"),
            case("test:5000/repo", "test:5000", "test:5000", "test:5000/repo:latest"),
            case(
                "sub-dom1.foo.com/bar/baz/quux",
                "sub-dom1.foo.com",
                "sub-dom1.foo.com",
                "sub-dom1.foo.com/bar/baz/quux:latest"
            ),
            case(
                "b.gcr.io/test.example.com/my-app:test.example.com",
                "b.gcr.io",
                "b.gcr.io",
                "b.gcr.io/test.example.com/my-app:test.example.com"
            )
        )]
        fn test_mirror_registry(input: &str, registry: &str, resolved_registry: &str, whole: &str) {
            let mut reference = Reference::try_from(input).expect("could not parse reference");
            assert_eq!(resolved_registry, reference.resolve_registry());
            assert_eq!(registry, reference.registry());
            assert_eq!(None, reference.namespace());
            assert_eq!(whole, reference.whole());

            reference.set_mirror_registry("docker.mirror.io".to_owned());
            assert_eq!("docker.mirror.io", reference.resolve_registry());
            assert_eq!(registry, reference.registry());
            assert_eq!(Some(registry), reference.namespace());
            assert_eq!(whole, reference.whole());
        }

        #[rstest(
            expected, registry, repository, tag, digest,
            case(
                "docker.io/foo/bar:1.2@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 
                "docker.io", 
                "foo/bar", 
                "1.2", 
                "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
            )
        )]
        fn test_create_reference_from_tag_and_digest(
            expected: &str,
            registry: &str,
            repository: &str,
            tag: &str,
            digest: &str,
        ) {
            let reference = Reference::with_tag_and_digest(
                registry.to_string(),
                repository.to_string(),
                tag.to_string(),
                digest.to_string(),
            );
            assert_eq!(expected, reference.to_string());
        }
    }
}


================================================
FILE: src/distribution/repository.rs
================================================
//! Repository types of the distribution spec.

use crate::error::OciSpecError;
use derive_builder::Builder;
use getset::{Getters, Setters};
use serde::{Deserialize, Serialize};

#[derive(Builder, Clone, Debug, Deserialize, Eq, Getters, Setters, PartialEq, Serialize)]
#[builder(
    pattern = "owned",
    setter(into, strip_option),
    build_fn(error = "OciSpecError")
)]
#[getset(get = "pub", set = "pub")]
/// RepositoryList returns a catalog of repositories maintained on the registry.
pub struct RepositoryList {
    /// The items of the RepositoryList.
    repositories: Vec<String>,
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::error::Result;

    #[test]
    fn repository_list_success() -> Result<()> {
        let list = RepositoryListBuilder::default()
            .repositories(vec![])
            .build()?;
        assert!(list.repositories().is_empty());
        Ok(())
    }

    #[test]
    fn repository_list_failure() {
        assert!(RepositoryListBuilder::default().build().is_err());
    }
}


================================================
FILE: src/distribution/tag.rs
================================================
//! Tag types of the distribution spec.

use crate::error::OciSpecError;
use derive_builder::Builder;
use getset::{Getters, Setters};
use serde::{Deserialize, Serialize};

#[derive(Builder, Clone, Debug, Deserialize, Eq, Getters, Setters, PartialEq, Serialize)]
#[builder(
    pattern = "owned",
    setter(into, strip_option),
    build_fn(error = "OciSpecError")
)]
#[getset(get = "pub", set = "pub")]
/// A list of tags for a given repository.
pub struct TagList {
    /// The namespace of the repository.
    name: String,

    /// Each tags on the repository.
    tags: Vec<String>,
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::error::Result;

    #[test]
    fn tag_list_success() -> Result<()> {
        let list = TagListBuilder::default()
            .name("name")
            .tags(vec![])
            .build()?;
        assert!(list.tags().is_empty());
        assert_eq!(list.name(), "name");
        Ok(())
    }

    #[test]
    fn tag_list_failure() {
        assert!(TagListBuilder::default().build().is_err());
    }
}


================================================
FILE: src/distribution/version.rs
================================================
use const_format::formatcp;

/// API incompatible changes.
pub const VERSION_MAJOR: u32 = 1;

/// Changing functionality in a backwards-compatible manner
pub const VERSION_MINOR: u32 = 0;

/// Backwards-compatible bug fixes.
pub const VERSION_PATCH: u32 = 0;

/// Indicates development branch. Releases will be empty string.
pub const VERSION_DEV: &str = "-dev";

/// Retrieve the version as static str representation.
pub const VERSION: &str = formatcp!("{VERSION_MAJOR}.{VERSION_MINOR}.{VERSION_PATCH}{VERSION_DEV}");

/// Retrieve the version as string representation.
///
/// Use [`VERSION`] instead.
#[deprecated]
pub fn version() -> String {
    VERSION.to_owned()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[allow(deprecated)]
    fn version_test() {
        assert_eq!(version(), "1.0.0-dev".to_string())
    }
}


================================================
FILE: src/error.rs
================================================
//! Error types of the crate.

use std::{borrow::Cow, io};
use thiserror::Error;

/// Spezialized result type for oci spec operations. It is
/// used for any operation that might produce an error. This
/// typedef is generally used to avoid writing out
/// [OciSpecError] directly and is otherwise a direct mapping
/// to [Result](std::result::Result).
pub type Result<T> = std::result::Result<T, OciSpecError>;

/// Error type for oci spec errors.
#[derive(Error, Debug)]
pub enum OciSpecError {
    /// Will be returned if an error occurs that cannot
    /// be mapped to a more specialized error variant.
    #[error("{0}")]
    Other(String),

    /// Will be returned when an error happens during
    /// io operations.
    #[error("io operation failed")]
    Io(#[from] io::Error),

    /// Will be returned when an error happens during
    /// serialization or deserialization.
    #[error("serde failed")]
    SerDe(#[from] serde_json::Error),

    /// Builder specific errors.
    #[error("uninitialized field")]
    Builder(#[from] derive_builder::UninitializedFieldError),
}

pub(crate) fn oci_error<'a, M>(message: M) -> OciSpecError
where
    M: Into<Cow<'a, str>>,
{
    let message = message.into();
    match message {
        Cow::Borrowed(s) => OciSpecError::Other(s.to_owned()),
        Cow::Owned(s) => OciSpecError::Other(s),
    }
}


================================================
FILE: src/image/annotations.rs
================================================
/// AnnotationCreated is the annotation key for the date and time on which the
/// image was built (date-time string as defined by RFC 3339).
pub const ANNOTATION_CREATED: &str = "org.opencontainers.image.created";

/// AnnotationAuthors is the annotation key for the contact details of the
/// people or organization responsible for the image (freeform string).
pub const ANNOTATION_AUTHORS: &str = "org.opencontainers.image.authors";

/// AnnotationURL is the annotation key for the URL to find more information on
/// the image.
pub const ANNOTATION_URL: &str = "org.opencontainers.image.url";

/// AnnotationDocumentation is the annotation key for the URL to get
/// documentation on the image.
pub const ANNOTATION_DOCUMENTATION: &str = "org.opencontainers.image.documentation";

/// AnnotationSource is the annotation key for the URL to get source code for
/// building the image.
pub const ANNOTATION_SOURCE: &str = "org.opencontainers.image.source";

/// AnnotationVersion is the annotation key for the version of the packaged
/// software. The version MAY match a label or tag in the source code
/// repository. The version MAY be Semantic versioning-compatible.
pub const ANNOTATION_VERSION: &str = "org.opencontainers.image.version";

/// AnnotationRevision is the annotation key for the source control revision
/// identifier for the packaged software.
pub const ANNOTATION_REVISION: &str = "org.opencontainers.image.revision";

/// AnnotationVendor is the annotation key for the name of the distributing
/// entity, organization or individual.
pub const ANNOTATION_VENDOR: &str = "org.opencontainers.image.vendor";

/// AnnotationLicenses is the annotation key for the license(s) under which
/// contained software is distributed as an SPDX License Expression.
pub const ANNOTATION_LICENSES: &str = "org.opencontainers.image.licenses";

/// AnnotationRefName is the annotation key for the name of the reference for a
/// target. SHOULD only be considered valid when on descriptors on `index.json`
/// within image layout.
pub const ANNOTATION_REF_NAME: &str = "org.opencontainers.image.ref.name";

/// AnnotationTitle is the annotation key for the human-readable title of the
/// image.
pub const ANNOTATION_TITLE: &str = "org.opencontainers.image.title";

/// AnnotationDescription is the annotation key for the human-readable
/// description of the software packaged in the image.
pub const ANNOTATION_DESCRIPTION: &str = "org.opencontainers.image.description";

/// AnnotationBaseImageDigest is the annotation key for the digest of the
/// image's base image.
pub const ANNOTATION_BASE_IMAGE_DIGEST: &str = "org.opencontainers.image.base.digest";

/// AnnotationBaseImageName is the annotation key for the image reference of the
/// image's base image.
pub const ANNOTATION_BASE_IMAGE_NAME: &str = "org.opencontainers.image.base.name";


================================================
FILE: src/image/artifact.rs
================================================
use super::{Descriptor, MediaType};
use crate::error::{OciSpecError, Result};
use derive_builder::Builder;
use getset::{Getters, MutGetters, Setters};
use serde::{Deserialize, Serialize};
use std::{
    collections::HashMap,
    io::{Read, Write},
    path::Path,
};

#[derive(
    Builder, Clone, Debug, Deserialize, Eq, Getters, MutGetters, Setters, PartialEq, Serialize,
)]
#[serde(rename_all = "camelCase")]
#[builder(
    pattern = "owned",
    setter(into, strip_option),
    build_fn(error = "OciSpecError")
)]
/// The OCI Artifact manifest describes content addressable artifacts
/// in order to store them along side container images in a registry.
pub struct ArtifactManifest {
    /// This property MUST be used and contain the media type
    /// `application/vnd.oci.artifact.manifest.v1+json`.
    #[getset(get = "pub")]
    #[builder(default = "MediaType::ArtifactManifest")]
    #[builder(setter(skip))]
    media_type: MediaType,

    /// This property SHOULD be used and contain
    /// the mediaType of the referenced artifact.
    /// If defined, the value MUST comply with RFC 6838,
    /// including the naming requirements in its section 4.2,
    /// and MAY be registered with IANA.
    #[getset(get = "pub", set = "pub")]
    artifact_type: MediaType,

    /// This OPTIONAL property is an array of objects and each item
    /// in the array MUST be a descriptor. Each descriptor represents
    /// an artifact of any IANA mediaType. The list MAY be ordered
    /// for certain artifact types like scan results.
    #[getset(get_mut = "pub", get = "pub", set = "pub")]
    #[builder(default)]
    blobs: Vec<Descriptor>,

    /// This OPTIONAL property specifies a descriptor of another manifest.
    /// This value, used by the referrers API, indicates a relationship
    /// to the specified manifest.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[getset(get = "pub", set = "pub")]
    #[builder(default)]
    subject: Option<Descriptor>,

    /// This OPTIONAL property contains additional metadata for the artifact
    /// manifest. This OPTIONAL property MUST use the annotation rules.
    /// See Pre-Defined Annotation Keys. Annotations MAY be used to filter
    /// the response from the referrers API.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[getset(get_mut = "pub", get = "pub", set = "pub")]
    #[builder(default)]
    annotations: Option<HashMap<String, String>>,
}

impl ArtifactManifest {
    /// Attempts to load an image manifest from a file.
    ///
    /// # Errors
    ///
    /// - [OciSpecError::Io] if the file does not exist
    /// - [OciSpecError::SerDe] if the image manifest cannot be deserialized.
    ///
    /// # Example
    ///
    /// ``` no_run
    /// use oci_spec::image::ArtifactManifest;
    ///
    /// let artifact_manifest = ArtifactManifest::from_file("manifest.json").unwrap();
    /// ```
    pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
        crate::from_file(path)
    }

    /// Attempts to load an image manifest from a stream.
    ///
    /// # Errors
    ///
    /// - [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the manifest cannot be deserialized.
    ///
    /// # Example
    ///
    /// ``` no_run
    /// use oci_spec::image::ArtifactManifest;
    /// use std::fs::File;
    ///
    /// let reader = File::open("manifest.json").unwrap();
    /// let artifact_manifest = ArtifactManifest::from_reader(reader).unwrap();
    /// ```
    pub fn from_reader<R: Read>(reader: R) -> Result<Self> {
        crate::from_reader(reader)
    }

    /// Attempts to write an image manifest to a file as JSON. If the file already exists, it
    /// will be overwritten.
    ///
    /// # Errors
    ///
    /// - [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the image manifest cannot be serialized.
    ///
    /// # Example
    ///
    /// ``` no_run
    /// use oci_spec::image::ArtifactManifest;
    ///
    /// let artifact_manifest = ArtifactManifest::from_file("manifest.json").unwrap();
    /// artifact_manifest.to_file("my-manifest.json").unwrap();
    /// ```
    pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
        crate::to_file(&self, path, false)
    }

    /// Attempts to write an image manifest to a file as pretty printed JSON. If the file already exists, it
    /// will be overwritten.
    ///
    /// # Errors
    ///
    /// - [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the image manifest cannot be serialized.
    ///
    /// # Example
    ///
    /// ``` no_run
    /// use oci_spec::image::ArtifactManifest;
    ///
    /// let artifact_manifest = ArtifactManifest::from_file("manifest.json").unwrap();
    /// artifact_manifest.to_file_pretty("my-manifest.json").unwrap();
    /// ```
    pub fn to_file_pretty<P: AsRef<Path>>(&self, path: P) -> Result<()> {
        crate::to_file(&self, path, true)
    }

    /// Attempts to write an image manifest to a stream as JSON.
    ///
    /// # Errors
    ///
    /// - [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the image manifest cannot be serialized.
    ///
    /// # Example
    ///
    /// ``` no_run
    /// use oci_spec::image::ArtifactManifest;
    ///
    /// let artifact_manifest = ArtifactManifest::from_file("manifest.json").unwrap();
    /// let mut writer = Vec::new();
    /// artifact_manifest.to_writer(&mut writer);
    /// ```
    pub fn to_writer<W: Write>(&self, writer: &mut W) -> Result<()> {
        crate::to_writer(&self, writer, false)
    }

    /// Attempts to write an image manifest to a stream as pretty printed JSON.
    ///
    /// # Errors
    ///
    /// - [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the image manifest cannot be serialized.
    ///
    /// # Example
    ///
    /// ``` no_run
    /// use oci_spec::image::ArtifactManifest;
    ///
    /// let artifact_manifest = ArtifactManifest::from_file("manifest.json").unwrap();
    /// let mut writer = Vec::new();
    /// artifact_manifest.to_writer_pretty(&mut writer);
    /// ```
    pub fn to_writer_pretty<W: Write>(&self, writer: &mut W) -> Result<()> {
        crate::to_writer(&self, writer, true)
    }

    /// Attempts to write an image manifest to a string as JSON.
    ///
    /// # Errors
    ///
    /// - [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the image configuration cannot be serialized.
    ///
    /// # Example
    ///
    /// ``` no_run
    /// use oci_spec::image::ArtifactManifest;
    ///
    /// let artifact_manifest = ArtifactManifest::from_file("manifest.json").unwrap();
    /// let json_str = artifact_manifest.to_string().unwrap();
    /// ```
    pub fn to_string(&self) -> Result<String> {
        crate::to_string(&self, false)
    }

    /// Attempts to write an image manifest to a string as pretty printed JSON.
    ///
    /// # Errors
    ///
    /// - [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the image configuration cannot be serialized.
    ///
    /// # Example
    ///
    /// ``` no_run
    /// use oci_spec::image::ArtifactManifest;
    ///
    /// let artifact_manifest = ArtifactManifest::from_file("manifest.json").unwrap();
    /// let json_str = artifact_manifest.to_string_pretty().unwrap();
    /// ```
    pub fn to_string_pretty(&self) -> Result<String> {
        crate::to_string(&self, true)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::image::{DescriptorBuilder, Sha256Digest};
    use std::{path::PathBuf, str::FromStr};

    fn get_manifest_path() -> PathBuf {
        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test/data/artifact_manifest.json")
    }

    fn create_manifest() -> ArtifactManifest {
        let blob = DescriptorBuilder::default()
            .media_type(MediaType::Other("application/gzip".to_string()))
            .size(123u64)
            .digest(
                Sha256Digest::from_str(
                    "87923725d74f4bfb94c9e86d64170f7521aad8221a5de834851470ca142da630",
                )
                .unwrap(),
            )
            .build()
            .unwrap();
        let subject = DescriptorBuilder::default()
            .media_type(MediaType::ImageManifest)
            .size(1234u64)
            .digest(
                Sha256Digest::from_str(
                    "cc06a2839488b8bd2a2b99dcdc03d5cfd818eed72ad08ef3cc197aac64c0d0a0",
                )
                .unwrap(),
            )
            .build()
            .unwrap();
        let annotations = HashMap::from([
            (
                "org.opencontainers.artifact.created".to_string(),
                "2022-01-01T14:42:55Z".to_string(),
            ),
            ("org.example.sbom.format".to_string(), "json".to_string()),
        ]);
        ArtifactManifestBuilder::default()
            .artifact_type(MediaType::Other(
                "application/vnd.example.sbom.v1".to_string(),
            ))
            .blobs(vec![blob])
            .subject(subject)
            .annotations(annotations)
            .build()
            .unwrap()
    }

    #[test]
    fn load_manifest_from_file() {
        // arrange
        let manifest_path = get_manifest_path();
        let expected = create_manifest();

        // act
        let actual = ArtifactManifest::from_file(manifest_path).expect("from file");

        // assert
        assert_eq!(actual, expected);
    }
}


================================================
FILE: src/image/config.rs
================================================
use super::{Arch, Os};
use crate::{
    error::{OciSpecError, Result},
    from_file, from_reader, to_file, to_string, to_writer,
};
use derive_builder::Builder;
use getset::{CopyGetters, Getters, MutGetters, Setters};
use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
#[cfg(test)]
use std::collections::BTreeMap;
use std::{
    collections::HashMap,
    fmt::Display,
    io::{Read, Write},
    path::Path,
};

/// In theory, this key is not standard.  In practice, it's used by at least the
/// RHEL UBI images for a long time.
pub const LABEL_VERSION: &str = "version";

#[derive(
    Builder,
    Clone,
    Debug,
    Default,
    Deserialize,
    Eq,
    Getters,
    MutGetters,
    Setters,
    PartialEq,
    Serialize,
)]
#[builder(
    default,
    pattern = "owned",
    setter(into, strip_option),
    build_fn(error = "OciSpecError")
)]
#[getset(get = "pub", set = "pub")]
/// The image configuration is associated with an image and describes some
/// basic information about the image such as date created, author, as
/// well as execution/runtime configuration like its entrypoint, default
/// arguments, networking, and volumes.
pub struct ImageConfiguration {
    /// An combined date and time at which the image was created,
    /// formatted as defined by [RFC 3339, section 5.6.](https://tools.ietf.org/html/rfc3339#section-5.6)
    #[serde(skip_serializing_if = "Option::is_none")]
    created: Option<String>,
    /// Gives the name and/or email address of the person or entity
    /// which created and is responsible for maintaining the image.
    #[serde(skip_serializing_if = "Option::is_none")]
    author: Option<String>,
    /// The CPU architecture which the binaries in this
    /// image are built to run on. Configurations SHOULD use, and
    /// implementations SHOULD understand, values listed in the Go
    /// Language document for [GOARCH](https://golang.org/doc/install/source#environment).
    architecture: Arch,
    /// The name of the operating system which the image is built to run on.
    /// Configurations SHOULD use, and implementations SHOULD understand,
    /// values listed in the Go Language document for [GOOS](https://golang.org/doc/install/source#environment).
    os: Os,
    /// This OPTIONAL property specifies the version of the operating
    /// system targeted by the referenced blob. Implementations MAY refuse
    /// to use manifests where os.version is not known to work with
    /// the host OS version. Valid values are
    /// implementation-defined. e.g. 10.0.14393.1066 on windows.
    #[serde(rename = "os.version", skip_serializing_if = "Option::is_none")]
    os_version: Option<String>,
    /// This OPTIONAL property specifies an array of strings,
    /// each specifying a mandatory OS feature. When os is windows, image
    /// indexes SHOULD use, and implementations SHOULD understand
    /// the following values:
    /// - win32k: image requires win32k.sys on the host (Note: win32k.sys is
    ///   missing on Nano Server)
    #[serde(rename = "os.features", skip_serializing_if = "Option::is_none")]
    os_features: Option<Vec<String>>,
    /// The variant of the specified CPU architecture. Configurations SHOULD
    /// use, and implementations SHOULD understand, variant values
    /// listed in the [Platform Variants](https://github.com/opencontainers/image-spec/blob/main/image-index.md#platform-variants) table.
    #[serde(skip_serializing_if = "Option::is_none")]
    variant: Option<String>,
    /// The execution parameters which SHOULD be used as a base when
    /// running a container using the image. This field can be None, in
    /// which case any execution parameters should be specified at
    /// creation of the container.
    #[serde(skip_serializing_if = "Option::is_none")]
    config: Option<Config>,
    /// The rootfs key references the layer content addresses used by the
    /// image. This makes the image config hash depend on the
    /// filesystem hash.
    #[getset(get_mut = "pub", get = "pub", set = "pub")]
    rootfs: RootFs,
    /// Describes the history of each layer. The array is ordered from first
    /// to last.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[getset(get_mut = "pub", get = "pub", set = "pub")]
    history: Option<Vec<History>>,
}

impl ImageConfiguration {
    /// Attempts to load an image configuration from a file.
    /// # Errors
    /// This function will return an [OciSpecError::Io](crate::OciSpecError::Io)
    /// if the file does not exist or an
    /// [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the image configuration
    /// cannot be deserialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::ImageConfiguration;
    ///
    /// let image_index = ImageConfiguration::from_file("config.json").unwrap();
    /// ```
    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<ImageConfiguration> {
        from_file(path)
    }

    /// Attempts to load an image configuration from a stream.
    /// # Errors
    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe)
    /// if the image configuration cannot be deserialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::ImageConfiguration;
    /// use std::fs::File;
    ///
    /// let reader = File::open("config.json").unwrap();
    /// let image_index = ImageConfiguration::from_reader(reader).unwrap();
    /// ```
    pub fn from_reader<R: Read>(reader: R) -> Result<ImageConfiguration> {
        from_reader(reader)
    }

    /// Attempts to write an image configuration to a file as JSON. If the file already exists, it
    /// will be overwritten.
    /// # Errors
    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if
    /// the image configuration cannot be serialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::ImageConfiguration;
    ///
    /// let image_index = ImageConfiguration::from_file("config.json").unwrap();
    /// image_index.to_file("my-config.json").unwrap();
    /// ```
    pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
        to_file(&self, path, false)
    }

    /// Attempts to write an image configuration to a file as pretty printed JSON. If the file
    /// already exists, it will be overwritten.
    /// # Errors
    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if
    /// the image configuration cannot be serialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::ImageConfiguration;
    ///
    /// let image_index = ImageConfiguration::from_file("config.json").unwrap();
    /// image_index.to_file_pretty("my-config.json").unwrap();
    /// ```
    pub fn to_file_pretty<P: AsRef<Path>>(&self, path: P) -> Result<()> {
        to_file(&self, path, true)
    }

    /// Attempts to write an image configuration to a stream as JSON.
    /// # Errors
    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if
    /// the image configuration cannot be serialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::ImageConfiguration;
    ///
    /// let image_index = ImageConfiguration::from_file("config.json").unwrap();
    /// let mut writer = Vec::new();
    /// image_index.to_writer(&mut writer);
    /// ```
    pub fn to_writer<W: Write>(&self, writer: &mut W) -> Result<()> {
        to_writer(&self, writer, false)
    }

    /// Attempts to write an image configuration to a stream as pretty printed JSON.
    /// # Errors
    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if
    /// the image configuration cannot be serialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::ImageConfiguration;
    ///
    /// let image_index = ImageConfiguration::from_file("config.json").unwrap();
    /// let mut writer = Vec::new();
    /// image_index.to_writer_pretty(&mut writer);
    /// ```
    pub fn to_writer_pretty<W: Write>(&self, writer: &mut W) -> Result<()> {
        to_writer(&self, writer, true)
    }

    /// Attempts to write an image configuration to a string as JSON.
    /// # Errors
    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if
    /// the image configuration cannot be serialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::ImageConfiguration;
    ///
    /// let image_configuration = ImageConfiguration::from_file("config.json").unwrap();
    /// let json_str = image_configuration.to_string().unwrap();
    /// ```
    pub fn to_string(&self) -> Result<String> {
        to_string(&self, false)
    }

    /// Attempts to write an image configuration to a string as pretty printed JSON.
    /// # Errors
    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if
    /// the image configuration cannot be serialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::ImageConfiguration;
    ///
    /// let image_configuration = ImageConfiguration::from_file("config.json").unwrap();
    /// let json_str = image_configuration.to_string_pretty().unwrap();
    /// ```
    pub fn to_string_pretty(&self) -> Result<String> {
        to_string(&self, true)
    }

    /// Extract the labels of the configuration, if present.
    pub fn labels_of_config(&self) -> Option<&HashMap<String, String>> {
        self.config().as_ref().and_then(|c| c.labels().as_ref())
    }

    /// Retrieve the version number associated with this configuration.  This will try
    /// to use several well-known label keys.
    pub fn version(&self) -> Option<&str> {
        let labels = self.labels_of_config();
        if let Some(labels) = labels {
            for k in [super::ANNOTATION_VERSION, LABEL_VERSION] {
                if let Some(v) = labels.get(k) {
                    return Some(v.as_str());
                }
            }
        }
        None
    }

    /// Extract the value of a given annotation on the configuration, if present.
    pub fn get_config_annotation(&self, key: &str) -> Option<&str> {
        self.labels_of_config()
            .and_then(|v| v.get(key).map(|s| s.as_str()))
    }
}

/// This ToString trait is automatically implemented for any type which implements the Display trait.
/// As such, ToString shouldn’t be implemented directly: Display should be implemented instead,
/// and you get the ToString implementation for free.
impl Display for ImageConfiguration {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        // Serde serialization never fails since this is
        // a combination of String and enums.
        write!(
            f,
            "{}",
            self.to_string_pretty()
                .expect("ImageConfiguration JSON conversion failed")
        )
    }
}

#[derive(
    Builder,
    Clone,
    Debug,
    Default,
    Deserialize,
    Eq,
    Getters,
    MutGetters,
    Setters,
    PartialEq,
    Serialize,
)]
#[serde(rename_all = "PascalCase")]
#[builder(
    default,
    pattern = "owned",
    setter(into, strip_option),
    build_fn(error = "OciSpecError")
)]
#[getset(get = "pub", set = "pub")]
/// The execution parameters which SHOULD be used as a base when
/// running a container using the image.
pub struct Config {
    /// The username or UID which is a platform-specific
    /// structure that allows specific control over which
    /// user the process run as. This acts as a default
    /// value to use when the value is not specified when
    /// creating a container. For Linux based systems, all
    /// of the following are valid: user, uid, user:group,
    /// uid:gid, uid:group, user:gid. If group/gid is not
    /// specified, the default group and supplementary
    /// groups of the given user/uid in /etc/passwd from
    /// the container are applied.
    #[serde(skip_serializing_if = "Option::is_none")]
    user: Option<String>,
    /// A set of ports to expose from a container running this
    /// image. Its keys can be in the format of: port/tcp, port/udp,
    /// port with the default protocol being tcp if not specified.
    /// These values act as defaults and are merged with any
    /// specified when creating a container.
    #[serde(
        default,
        skip_serializing_if = "Option::is_none",
        deserialize_with = "deserialize_as_vec",
        serialize_with = "serialize_as_map"
    )]
    exposed_ports: Option<Vec<String>>,
    /// Entries are in the format of VARNAME=VARVALUE. These
    /// values act as defaults and are merged with any
    /// specified when creating a container.
    #[serde(skip_serializing_if = "Option::is_none")]
    env: Option<Vec<String>>,
    /// A list of arguments to use as the command to execute
    /// when the container starts. These values act as defaults
    /// and may be replaced by an entrypoint specified when
    /// creating a container.
    #[serde(skip_serializing_if = "Option::is_none")]
    entrypoint: Option<Vec<String>>,
    /// Default arguments to the entrypoint of the container.
    /// These values act as defaults and may be replaced by any
    /// specified when creating a container. If an Entrypoint
    /// value is not specified, then the first entry of the Cmd
    /// array SHOULD be interpreted as the executable to run.
    #[serde(skip_serializing_if = "Option::is_none")]
    cmd: Option<Vec<String>>,
    /// A set of directories describing where the process is
    /// likely to write data specific to a container instance.
    #[serde(
        default,
        skip_serializing_if = "Option::is_none",
        deserialize_with = "deserialize_as_vec",
        serialize_with = "serialize_as_map"
    )]
    volumes: Option<Vec<String>>,
    /// Sets the current working directory of the entrypoint process
    /// in the container. This value acts as a default and may be
    /// replaced by a working directory specified when creating
    /// a container.
    #[serde(skip_serializing_if = "Option::is_none")]
    working_dir: Option<String>,
    /// The field contains arbitrary metadata for the container.
    /// This property MUST use the annotation rules.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[getset(get_mut = "pub", get = "pub", set = "pub")]
    labels: Option<HashMap<String, String>>,
    /// The field contains the system call signal that will be
    /// sent to the container to exit. The signal can be a signal
    /// name in the format SIGNAME, for instance SIGKILL or SIGRTMIN+3.
    #[serde(skip_serializing_if = "Option::is_none")]
    stop_signal: Option<String>,
}

// Some fields of the image configuration are a json serialization of a
// Go map[string]struct{} leading to the following json:
// {
//    "ExposedPorts": {
//       "8080/tcp": {},
//       "443/tcp": {},
//    }
// }
// Instead we treat this as a list
#[derive(Deserialize, Serialize)]
struct GoMapSerde {}

fn deserialize_as_vec<'de, D>(deserializer: D) -> std::result::Result<Option<Vec<String>>, D::Error>
where
    D: Deserializer<'de>,
{
    // ensure stable order of keys in json document for comparison between expected and actual
    #[cfg(test)]
    let opt = Option::<BTreeMap<String, GoMapSerde>>::deserialize(deserializer)?;
    #[cfg(not(test))]
    let opt = Option::<HashMap<String, GoMapSerde>>::deserialize(deserializer)?;

    if let Some(data) = opt {
        let vec: Vec<String> = data.keys().cloned().collect();
        return Ok(Some(vec));
    }

    Ok(None)
}

fn serialize_as_map<S>(
    target: &Option<Vec<String>>,
    serializer: S,
) -> std::result::Result<S::Ok, S::Error>
where
    S: Serializer,
{
    match target {
        Some(values) => {
            // ensure stable order of keys in json document for comparison between expected and actual
            #[cfg(test)]
            let map: BTreeMap<_, _> = values.iter().map(|v| (v, GoMapSerde {})).collect();
            #[cfg(not(test))]
            let map: HashMap<_, _> = values.iter().map(|v| (v, GoMapSerde {})).collect();

            let mut map_ser = serializer.serialize_map(Some(map.len()))?;
            for (key, value) in map {
                map_ser.serialize_entry(key, &value)?;
            }
            map_ser.end()
        }
        _ => unreachable!(),
    }
}

#[derive(
    Builder, Clone, Debug, Deserialize, Eq, Getters, MutGetters, Setters, PartialEq, Serialize,
)]
#[builder(
    default,
    pattern = "owned",
    setter(into, strip_option),
    build_fn(error = "OciSpecError")
)]
#[getset(get = "pub", set = "pub")]
/// RootFs references the layer content addresses used by the image.
pub struct RootFs {
    /// MUST be set to layers.
    #[serde(rename = "type")]
    typ: String,
    /// An array of layer content hashes (DiffIDs), in order
    /// from first to last.
    #[getset(get_mut = "pub", get = "pub", set = "pub")]
    diff_ids: Vec<String>,
}

impl Default for RootFs {
    fn default() -> Self {
        Self {
            typ: "layers".to_owned(),
            diff_ids: Default::default(),
        }
    }
}

#[derive(
    Builder,
    Clone,
    Debug,
    Default,
    Deserialize,
    Eq,
    CopyGetters,
    Getters,
    Setters,
    PartialEq,
    Serialize,
)]
#[builder(
    default,
    pattern = "owned",
    setter(into, strip_option),
    build_fn(error = "OciSpecError")
)]
/// Describes the history of a layer.
pub struct History {
    /// A combined date and time at which the layer was created,
    /// formatted as defined by [RFC 3339, section 5.6.](https://tools.ietf.org/html/rfc3339#section-5.6).
    #[serde(skip_serializing_if = "Option::is_none")]
    #[getset(get = "pub", set = "pub")]
    created: Option<String>,
    /// The author of the build point.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[getset(get = "pub", set = "pub")]
    author: Option<String>,
    /// The command which created the layer.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[getset(get = "pub", set = "pub")]
    created_by: Option<String>,
    /// A custom message set when creating the layer.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[getset(get = "pub", set = "pub")]
    comment: Option<String>,
    /// This field is used to mark if the history item created
    /// a filesystem diff. It is set to true if this history item
    /// doesn't correspond to an actual layer in the rootfs section
    #[serde(skip_serializing_if = "Option::is_none")]
    #[getset(get_copy = "pub", set = "pub")]
    empty_layer: Option<bool>,
}

#[cfg(test)]
mod tests {
    use std::{fs, path::PathBuf};

    use super::*;
    use crate::image::{ANNOTATION_CREATED, ANNOTATION_VERSION};

    fn create_base_config() -> ConfigBuilder {
        ConfigBuilder::default()
            .user("alice".to_owned())
            .exposed_ports(vec!["8080/tcp".to_owned()])
            .env(vec![
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin".to_owned(),
                "FOO=oci_is_a".to_owned(),
                "BAR=well_written_spec".to_owned(),
            ])
            .entrypoint(vec!["/bin/my-app-binary".to_owned()])
            .cmd(vec![
                "--foreground".to_owned(),
                "--config".to_owned(),
                "/etc/my-app.d/default.cfg".to_owned(),
            ])
            .volumes(vec![
                "/var/job-result-data".to_owned(),
                "/var/log/my-app-logs".to_owned(),
            ])
            .working_dir("/home/alice".to_owned())
    }

    fn create_base_imgconfig(conf: Config) -> ImageConfigurationBuilder {
        ImageConfigurationBuilder::default()
            .created("2015-10-31T22:22:56.015925234Z".to_owned())
            .author("Alyssa P. Hacker <alyspdev@example.com>".to_owned())
            .architecture(Arch::Amd64)
            .os(Os::Linux)
            .config(conf
            )
            .rootfs(RootFsBuilder::default()
            .diff_ids(vec![
                "sha256:c6f988f4874bb0add23a778f753c65efe992244e148a1d2ec2a8b664fb66bbd1".to_owned(),
                "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef".to_owned(),
            ])
            .build()
            .expect("build rootfs"))
            .history(vec![
                HistoryBuilder::default()
                .created("2015-10-31T22:22:54.690851953Z".to_owned())
                .created_by("/bin/sh -c #(nop) ADD file:a3bc1e842b69636f9df5256c49c5374fb4eef1e281fe3f282c65fb853ee171c5 in /".to_owned())
                .build()
                .expect("build history"),
                HistoryBuilder::default()
                .created("2015-10-31T22:22:55.613815829Z".to_owned())
                .created_by("/bin/sh -c #(nop) CMD [\"sh\"]".to_owned())
                .empty_layer(true)
                .build()
                .expect("build history"),
            ])
    }

    fn create_config() -> ImageConfiguration {
        create_base_imgconfig(create_base_config().build().expect("config"))
            .build()
            .expect("build configuration")
    }

    /// A config with some additions (labels)
    fn create_imgconfig_v1() -> ImageConfiguration {
        let labels = [
            (ANNOTATION_CREATED, "2023-09-16T19:22:18.014Z"),
            (ANNOTATION_VERSION, "42.27"),
        ]
        .into_iter()
        .map(|(k, v)| (k.to_owned(), v.to_owned()));
        let config = create_base_config()
            .labels(labels.collect::<HashMap<_, _>>())
            .build()
            .unwrap();
        create_base_imgconfig(config).build().unwrap()
    }

    fn get_config_path() -> PathBuf {
        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test/data/config.json")
    }

    #[test]
    fn load_configuration_from_file() {
        // arrange
        let config_path = get_config_path();
        let expected = create_config();

        // act
        let actual = ImageConfiguration::from_file(config_path).expect("from file");

        // assert
        assert_eq!(actual, expected);
    }

    #[test]
    fn test_helpers() {
        let config = create_imgconfig_v1();
        assert_eq!(config.labels_of_config().unwrap().len(), 2);
        assert_eq!(
            config.get_config_annotation(ANNOTATION_CREATED).unwrap(),
            "2023-09-16T19:22:18.014Z"
        );
    }

    #[test]
    fn load_configuration_from_reader() {
        // arrange
        let reader = fs::read(get_config_path()).expect("read config");

        // act
        let actual = ImageConfiguration::from_reader(&*reader).expect("from reader");
        println!("{actual:#?}");

        // assert
        let expected = create_config();
        println!("{expected:#?}");

        assert_eq!(actual, expected);
    }

    #[test]
    fn save_config_to_file() {
        // arrange
        let tmp = std::env::temp_dir().join("save_config_to_file");
        fs::create_dir_all(&tmp).expect("create test directory");
        let config = create_config();
        let config_path = tmp.join("config.json");

        // act
        config
            .to_file_pretty(&config_path)
            .expect("write config to file");

        // assert
        let actual = fs::read_to_string(config_path).expect("read actual");
        let expected = fs::read_to_string(get_config_path()).expect("read expected");
        assert_eq!(actual, expected);
    }

    #[test]
    fn save_config_to_writer() {
        // arrange
        let config = create_config();
        let mut actual = Vec::new();

        // act
        config.to_writer_pretty(&mut actual).expect("to writer");
        let actual = String::from_utf8(actual).unwrap();

        // assert
        let expected = fs::read_to_string(get_config_path()).expect("read expected");
        assert_eq!(actual, expected);
    }

    #[test]
    fn save_config_to_string() {
        // arrange
        let config = create_config();

        // act
        let actual = config.to_string_pretty().expect("to string");

        // assert
        let expected = fs::read_to_string(get_config_path()).expect("read expected");
        assert_eq!(actual, expected);
    }

    #[test]
    fn optional_history_field_absent() {
        let json = r#"{
            "architecture": "amd64",
            "os": "linux",
            "rootfs": {
                "type": "layers",
                "diff_ids": ["sha256:abc123"]
            }
        }"#;

        let config: ImageConfiguration =
            serde_json::from_str(json).expect("deserialize without history");
        assert!(config.history().is_none());
    }

    #[test]
    fn serialize_without_history() {
        let config = ImageConfigurationBuilder::default()
            .architecture(Arch::Amd64)
            .os(Os::Linux)
            .rootfs(
                RootFsBuilder::default()
                    .diff_ids(vec!["sha256:abc123".to_owned()])
                    .build()
                    .expect("build rootfs"),
            )
            .build()
            .expect("build config");

        let json = config.to_string().expect("serialize");
        assert!(!json.contains("history"));
    }

    #[test]
    fn builder_without_history() {
        let config = ImageConfigurationBuilder::default()
            .architecture(Arch::Amd64)
            .os(Os::Linux)
            .rootfs(
                RootFsBuilder::default()
                    .diff_ids(vec!["sha256:abc123".to_owned()])
                    .build()
                    .expect("build rootfs"),
            )
            .build()
            .expect("build config");

        assert!(config.history().is_none());
    }
}


================================================
FILE: src/image/descriptor.rs
================================================
use super::{Arch, Digest, MediaType, Os};
use crate::error::OciSpecError;
use derive_builder::Builder;
use getset::{CopyGetters, Getters, Setters};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

#[derive(
    Builder, Clone, CopyGetters, Debug, Deserialize, Eq, Getters, Setters, PartialEq, Serialize,
)]
#[serde(rename_all = "camelCase")]
#[builder(
    pattern = "owned",
    setter(into, strip_option),
    build_fn(error = "OciSpecError")
)]
/// A Content Descriptor (or simply Descriptor) describes the disposition of
/// the targeted content. It includes the type of the content, a content
/// identifier (digest), and the byte-size of the raw content.
/// Descriptors SHOULD be embedded in other formats to securely reference
/// external content.
pub struct Descriptor {
    /// This REQUIRED property contains the media type of the referenced
    /// content. Values MUST comply with RFC 6838, including the naming
    /// requirements in its section 4.2.
    #[getset(get = "pub", set = "pub")]
    media_type: MediaType,
    /// This REQUIRED property is the digest of the targeted content,
    /// conforming to the requirements outlined in Digests. Retrieved
    /// content SHOULD be verified against this digest when consumed via
    /// untrusted sources.
    #[getset(get = "pub", set = "pub")]
    digest: Digest,
    /// This REQUIRED property specifies the size, in bytes, of the raw
    /// content. This property exists so that a client will have an
    /// expected size for the content before processing. If the
    /// length of the retrieved content does not match the specified
    /// length, the content SHOULD NOT be trusted.
    #[getset(get_copy = "pub", set = "pub")]
    size: u64,
    /// This OPTIONAL property specifies a list of URIs from which this
    /// object MAY be downloaded. Each entry MUST conform to [RFC 3986](https://tools.ietf.org/html/rfc3986).
    /// Entries SHOULD use the http and https schemes, as defined
    /// in [RFC 7230](https://tools.ietf.org/html/rfc7230#section-2.7).
    #[serde(skip_serializing_if = "Option::is_none")]
    #[getset(get = "pub", set = "pub")]
    #[builder(default)]
    urls: Option<Vec<String>>,
    /// This OPTIONAL property contains arbitrary metadata for this
    /// descriptor. This OPTIONAL property MUST use the annotation
    /// rules.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[getset(get = "pub", set = "pub")]
    #[builder(default)]
    annotations: Option<HashMap<String, String>>,
    /// This OPTIONAL property describes the minimum runtime requirements of
    /// the image. This property SHOULD be present if its target is
    /// platform-specific.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[getset(get = "pub", set = "pub")]
    #[builder(default)]
    platform: Option<Platform>,
    /// This OPTIONAL property contains the type of an artifact when the descriptor points to an
    /// artifact. This is the value of the config descriptor mediaType when the descriptor
    /// references an image manifest. If defined, the value MUST comply with RFC 6838, including
    /// the naming requirements in its section 4.2, and MAY be registered with IANA.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[getset(get = "pub", set = "pub")]
    #[builder(default)]
    artifact_type: Option<MediaType>,
    /// This OPTIONAL property contains an embedded representation of the referenced content.
    /// Values MUST conform to the Base 64 encoding, as defined in RFC 4648. The decoded data MUST
    /// be identical to the referenced content and SHOULD be verified against the digest and size
    /// fields by content consumers. See Embedded Content for when this is appropriate.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[getset(get = "pub", set = "pub")]
    #[builder(default)]
    data: Option<String>,
}

#[derive(
    Builder, Clone, Debug, Default, Deserialize, Eq, Getters, Setters, PartialEq, Serialize,
)]
#[builder(
    pattern = "owned",
    setter(into, strip_option),
    build_fn(error = "OciSpecError")
)]
#[getset(get = "pub", set = "pub")]
/// Describes the minimum runtime requirements of the image.
pub struct Platform {
    /// This REQUIRED property specifies the CPU architecture.
    /// Image indexes SHOULD use, and implementations SHOULD understand,
    /// values listed in the Go Language document for GOARCH.
    architecture: Arch,
    /// This REQUIRED property specifies the operating system.
    /// Image indexes SHOULD use, and implementations SHOULD understand,
    /// values listed in the Go Language document for GOOS.
    os: Os,
    /// This OPTIONAL property specifies the version of the operating system
    /// targeted by the referenced blob. Implementations MAY refuse to use
    /// manifests where os.version is not known to work with the host OS
    /// version. Valid values are implementation-defined. e.g.
    /// 10.0.14393.1066 on windows.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(default)]
    os_version: Option<String>,
    /// This OPTIONAL property specifies an array of strings, each
    /// specifying a mandatory OS feature. When os is windows, image
    /// indexes SHOULD use, and implementations SHOULD understand
    /// the following values:
    /// - win32k: image requires win32k.sys on the host (Note: win32k.sys is
    ///   missing on Nano Server)
    ///
    /// When os is not windows, values are implementation-defined and SHOULD
    /// be submitted to this specification for standardization.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(default)]
    os_features: Option<Vec<String>>,
    /// This OPTIONAL property specifies the variant of the CPU.
    /// Image indexes SHOULD use, and implementations SHOULD understand,
    /// variant values listed in the [Platform Variants]
    /// (<https://github.com/opencontainers/image-spec/blob/main/image-index.md#platform-variants>)
    /// table.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(default)]
    variant: Option<String>,
    /// This property is RESERVED for future versions of the specification.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(default)]
    features: Option<Vec<String>>,
}

impl Descriptor {
    /// Construct a new descriptor with the required fields.
    pub fn new(media_type: MediaType, size: u64, digest: impl Into<Digest>) -> Self {
        Self {
            media_type,
            size,
            digest: digest.into(),
            urls: Default::default(),
            annotations: Default::default(),
            platform: Default::default(),
            artifact_type: Default::default(),
            data: Default::default(),
        }
    }

    /// Return a view of [`Self::digest()`] that has been parsed as a valid SHA-256.
    pub fn as_digest_sha256(&self) -> Option<&str> {
        match self.digest.algorithm() {
            super::DigestAlgorithm::Sha256 => Some(self.digest.digest()),
            _ => None,
        }
    }
}

#[cfg(test)]
mod tests {
    use std::str::FromStr;

    use super::*;

    #[test]
    fn test_deserialize() {
        let descriptor_str = r#"{
            "mediaType": "application/vnd.oci.image.manifest.v1+json",
            "digest":"sha256:c2b8beca588702777e5f35dafdbeae9ec16c2bab802331f81cacd2a92f1d5356",
            "size":769,
            "annotations":{"org.opencontainers.image.created": "2023-10-11T22:37:26Z"},
            "artifactType":"application/spdx+json"}"#;
        let descriptor: Descriptor = serde_json::from_str(descriptor_str).unwrap();
        assert_eq!(descriptor.media_type, MediaType::ImageManifest);
        assert_eq!(
            descriptor.digest,
            Digest::from_str(
                "sha256:c2b8beca588702777e5f35dafdbeae9ec16c2bab802331f81cacd2a92f1d5356"
            )
            .unwrap()
        );
        assert_eq!(descriptor.size, 769);
        assert_eq!(
            descriptor
                .annotations
                .unwrap()
                .get("org.opencontainers.image.created"),
            Some(&"2023-10-11T22:37:26Z".to_string())
        );
        assert_eq!(
            descriptor.artifact_type.unwrap(),
            MediaType::Other("application/spdx+json".to_string())
        );
    }

    #[test]
    fn test_malformed_digest() {
        let descriptor_str = r#"{
            "mediaType": "application/vnd.oci.image.manifest.v1+json",
            "digest":"../blah:this-is-an-attack",
            "size":769,
            "annotations":{"org.opencontainers.image.created": "2023-10-11T22:37:26Z"},
            "artifactType":"application/spdx+json"}"#;
        assert!(serde_json::from_str::<Descriptor>(descriptor_str).is_err());
    }
}


================================================
FILE: src/image/digest.rs
================================================
//! Functionality corresponding to <https://github.com/opencontainers/image-spec/blob/main/descriptor.md#digests>.

use std::fmt::Display;
use std::str::FromStr;

/// A digest algorithm; at the current time only SHA-256
/// is widely used and supported in the ecosystem. Other
/// SHA variants are included as they are noted in the
/// standards. Other digest algorithms may be added
/// in the future, so this structure is marked as non-exhaustive.
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum DigestAlgorithm {
    /// The SHA-256 algorithm.
    Sha256,
    /// The SHA-384 algorithm.
    Sha384,
    /// The SHA-512 algorithm.
    Sha512,
    /// Any other algorithm. Note that it is possible
    /// that other algorithms will be added as enum members.
    /// If you want to try to handle those, consider also
    /// comparing against [`Self::as_ref<str>`].
    Other(Box<str>),
}

impl AsRef<str> for DigestAlgorithm {
    fn as_ref(&self) -> &str {
        match self {
            DigestAlgorithm::Sha256 => "sha256",
            DigestAlgorithm::Sha384 => "sha384",
            DigestAlgorithm::Sha512 => "sha512",
            DigestAlgorithm::Other(o) => o,
        }
    }
}

impl Display for DigestAlgorithm {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(self.as_ref())
    }
}

impl DigestAlgorithm {
    /// Return the length of the digest in hexadecimal ASCII characters.
    pub const fn digest_hexlen(&self) -> Option<u32> {
        match self {
            DigestAlgorithm::Sha256 => Some(64),
            DigestAlgorithm::Sha384 => Some(96),
            DigestAlgorithm::Sha512 => Some(128),
            DigestAlgorithm::Other(_) => None,
        }
    }
}

impl From<&str> for DigestAlgorithm {
    fn from(value: &str) -> Self {
        match value {
            "sha256" => Self::Sha256,
            "sha384" => Self::Sha384,
            "sha512" => Self::Sha512,
            o => Self::Other(o.into()),
        }
    }
}

fn char_is_lowercase_ascii_hex(c: char) -> bool {
    matches!(c, '0'..='9' | 'a'..='f')
}

/// algorithm-component ::= [a-z0-9]+
fn char_is_algorithm_component(c: char) -> bool {
    matches!(c, 'a'..='z' | '0'..='9')
}

/// encoded ::= [a-zA-Z0-9=_-]+
fn char_is_encoded(c: char) -> bool {
    char_is_algorithm_component(c) || matches!(c, 'A'..='Z' | '=' | '_' | '-')
}

/// A parsed pair of algorithm:digest as defined
/// by <https://github.com/opencontainers/image-spec/blob/main/descriptor.md#digests>
///
/// ```
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// use std::str::FromStr;
/// use oci_spec::image::{Digest, DigestAlgorithm};
/// let d = Digest::from_str("sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b")?;
/// assert_eq!(d.algorithm(), &DigestAlgorithm::Sha256);
/// assert_eq!(d.digest(), "6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b");
/// let d = Digest::from_str("multihash+base58:QmRZxt2b1FVZPNqd8hsiykDL3TdBDeTSPX9Kv46HmX4Gx8")?;
/// assert_eq!(d.algorithm(), &DigestAlgorithm::from("multihash+base58"));
/// assert_eq!(d.digest(), "QmRZxt2b1FVZPNqd8hsiykDL3TdBDeTSPX9Kv46HmX4Gx8");
/// # Ok(())
/// # }
/// ```

#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct Digest {
    /// The algorithm; we need to hold a copy of this
    /// right now as we ended up returning a reference
    /// from the accessor. It probably would have been
    /// better to have both borrowed/owned DigestAlgorithm
    /// versions and our accessor just returns a borrowed version.
    algorithm: DigestAlgorithm,
    value: Box<str>,
    split: usize,
}

impl AsRef<str> for Digest {
    fn as_ref(&self) -> &str {
        &self.value
    }
}

impl Display for Digest {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(self.as_ref())
    }
}

impl<'de> serde::Deserialize<'de> for Digest {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        Self::from_str(&s).map_err(serde::de::Error::custom)
    }
}

impl serde::ser::Serialize for Digest {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let v = self.to_string();
        serializer.serialize_str(&v)
    }
}

impl Digest {
    const ALGORITHM_SEPARATOR: &'static [char] = &['+', '.', '_', '-'];
    /// The algorithm name (e.g. sha256, sha512)
    pub fn algorithm(&self) -> &DigestAlgorithm {
        &self.algorithm
    }

    /// The algorithm digest component. When this is one of the
    /// SHA family (SHA-256, SHA-384, etc.) the digest value
    /// is guaranteed to be a valid length with only lowercase hexadecimal
    /// characters. For example with SHA-256, the length is 64.
    pub fn digest(&self) -> &str {
        &self.value[self.split + 1..]
    }
}

impl FromStr for Digest {
    type Err = crate::OciSpecError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Digest::try_from(s)
    }
}

impl TryFrom<String> for Digest {
    type Error = crate::OciSpecError;

    fn try_from(s: String) -> Result<Self, Self::Error> {
        let s = s.into_boxed_str();
        let Some(split) = s.find(':') else {
            return Err(crate::OciSpecError::Other("missing ':' in digest".into()));
        };
        let (algorithm, value) = s.split_at(split);
        let value = &value[1..];

        // algorithm ::= algorithm-component (algorithm-separator algorithm-component)*
        let algorithm_parts = algorithm.split(Self::ALGORITHM_SEPARATOR);
        for part in algorithm_parts {
            if part.is_empty() {
                return Err(crate::OciSpecError::Other(
                    "Empty algorithm component".into(),
                ));
            }
            if !part.chars().all(char_is_algorithm_component) {
                return Err(crate::OciSpecError::Other(format!(
                    "Invalid algorithm component: {part}"
                )));
            }
        }

        if value.is_empty() {
            return Err(crate::OciSpecError::Other("Empty algorithm value".into()));
        }
        if !value.chars().all(char_is_encoded) {
            return Err(crate::OciSpecError::Other(format!(
                "Invalid encoded value {value}"
            )));
        }

        let algorithm = DigestAlgorithm::from(algorithm);
        if let Some(expected) = algorithm.digest_hexlen() {
            let found = value.len();
            if expected as usize != found {
                return Err(crate::OciSpecError::Other(format!(
                    "Invalid digest length {found} expected {expected}"
                )));
            }
            let is_all_hex = value.chars().all(char_is_lowercase_ascii_hex);
            if !is_all_hex {
                return Err(crate::OciSpecError::Other(format!(
                    "Invalid non-hexadecimal character in digest: {value}"
                )));
            }
        }
        Ok(Self {
            algorithm,
            value: s,
            split,
        })
    }
}

impl TryFrom<&str> for Digest {
    type Error = crate::OciSpecError;

    fn try_from(string: &str) -> Result<Self, Self::Error> {
        TryFrom::try_from(string.to_owned())
    }
}

/// A SHA-256 digest, guaranteed to be 64 lowercase hexadecimal ASCII characters.
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct Sha256Digest {
    digest: Box<str>,
}

impl From<Sha256Digest> for Digest {
    fn from(value: Sha256Digest) -> Self {
        Self {
            algorithm: DigestAlgorithm::Sha256,
            value: format!("sha256:{}", value.digest()).into(),
            split: 6,
        }
    }
}

impl AsRef<str> for Sha256Digest {
    fn as_ref(&self) -> &str {
        self.digest()
    }
}

impl Display for Sha256Digest {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(self.digest())
    }
}

impl FromStr for Sha256Digest {
    type Err = crate::OciSpecError;

    fn from_str(digest: &str) -> Result<Self, Self::Err> {
        let alg = DigestAlgorithm::Sha256;
        let v = format!("{alg}:{digest}");
        let d = Digest::from_str(&v)?;
        match d.algorithm {
            DigestAlgorithm::Sha256 => Ok(Self {
                digest: d.digest().into(),
            }),
            o => Err(crate::OciSpecError::Other(format!(
                "Expected algorithm sha256 but found {o}",
            ))),
        }
    }
}

impl Sha256Digest {
    /// The SHA-256 digest, guaranteed to be 64 lowercase hexadecimal characters.
    pub fn digest(&self) -> &str {
        &self.digest
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_digest_invalid() {
        let invalid = [
            "",
            "foo",
            ":",
            "blah+",
            "_digest:somevalue",
            ":blah",
            "blah:",
            "FooBar:123abc",
            "^:foo",
            "bar^baz:blah",
            "sha256:123456*78",
            "sha256:6c3c624b58dbbcd3c0dd82b4z53f04194d1247c6eebdaab7c610cf7d66709b3b", // has a z in the middle
            "sha384:x",
            "sha384:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b",
            "sha512:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b",
        ];
        for case in invalid {
            assert!(
                Digest::from_str(case).is_err(),
                "Should have failed to parse: {case}"
            )
        }
    }

    const VALID_DIGEST_SHA256: &str =
        "sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b";
    const VALID_DIGEST_SHA384: &str =
        "sha384:6c3c624b58dbbcd4d1247c6eebdaab7c610cf7d66709b3b3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b";
    const VALID_DIGEST_SHA512: &str =
        "sha512:6c3c624b58dbbcd3c0dd826c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3bb4c53f04194d1247c6eebdaab7c610cf7d66709b3b";

    #[test]
    fn test_digest_valid() {
        let cases = ["foo:bar", "xxhash:42"];
        for case in cases {
            Digest::from_str(case).unwrap();
        }

        let d = Digest::try_from("multihash+base58:QmRZxt2b1FVZPNqd8hsiykDL3TdBDeTSPX9Kv46HmX4Gx8")
            .unwrap();
        assert_eq!(d.algorithm(), &DigestAlgorithm::from("multihash+base58"));
        assert_eq!(d.digest(), "QmRZxt2b1FVZPNqd8hsiykDL3TdBDeTSPX9Kv46HmX4Gx8");
    }

    #[test]
    fn test_sha256_valid() {
        let expected_value = VALID_DIGEST_SHA256.split_once(':').unwrap().1;
        let d = Digest::from_str(VALID_DIGEST_SHA256).unwrap();
        assert_eq!(d.algorithm(), &DigestAlgorithm::Sha256);
        assert_eq!(d.digest(), expected_value);
        let base_digest = d.clone();
        assert_eq!(base_digest.digest(), expected_value);
    }

    #[test]
    fn test_sha384_valid() {
        let expected_value = VALID_DIGEST_SHA384.split_once(':').unwrap().1;
        let d = Digest::from_str(VALID_DIGEST_SHA384).unwrap();
        assert_eq!(d.algorithm(), &DigestAlgorithm::Sha384);
        assert_eq!(d.digest(), expected_value);
        // Verify we can cheaply coerce to a string
        assert_eq!(d.as_ref(), VALID_DIGEST_SHA384);
        let base_digest = d.clone();
        assert_eq!(base_digest.digest(), expected_value);
    }

    #[test]
    fn test_sha512_valid() {
        let expected_value = VALID_DIGEST_SHA512.split_once(':').unwrap().1;
        let d = Digest::from_str(VALID_DIGEST_SHA512).unwrap();
        assert_eq!(d.algorithm(), &DigestAlgorithm::Sha512);
        assert_eq!(d.digest(), expected_value);
        let base_digest = d.clone();
        assert_eq!(base_digest.digest(), expected_value);
    }

    #[test]
    fn test_sha256() {
        let digest = VALID_DIGEST_SHA256.split_once(':').unwrap().1;
        let v = Sha256Digest::from_str(digest).unwrap();
        assert_eq!(v.digest(), digest);
    }
}


================================================
FILE: src/image/index.rs
================================================
use super::{Descriptor, MediaType};
use crate::{
    error::{OciSpecError, Result},
    from_file, from_reader, to_file, to_string, to_writer,
};
use derive_builder::Builder;
use getset::{CopyGetters, Getters, Setters};
use serde::{Deserialize, Serialize};
use std::{
    collections::HashMap,
    fmt::Display,
    io::{Read, Write},
    path::Path,
};

/// The expected schema version; equals 2 for compatibility with older versions of Docker.
pub const SCHEMA_VERSION: u32 = 2;

#[derive(
    Builder, Clone, CopyGetters, Debug, Deserialize, Eq, Getters, Setters, PartialEq, Serialize,
)]
#[serde(rename_all = "camelCase")]
#[builder(
    pattern = "owned",
    setter(into, strip_option),
    build_fn(error = "OciSpecError")
)]
/// The image index is a higher-level manifest which points to specific
/// image manifests, ideal for one or more platforms. While the use of
/// an image index is OPTIONAL for image providers, image consumers
/// SHOULD be prepared to process them.
pub struct ImageIndex {
    /// This REQUIRED property specifies the image manifest schema version.
    /// For this version of the specification, this MUST be 2 to ensure
    /// backward compatibility with older versions of Docker. The
    /// value of this field will not change. This field MAY be
    /// removed in a future version of the specification.
    #[getset(get_copy = "pub", set = "pub")]
    schema_version: u32,
    /// This property is reserved for use, to maintain compatibility. When
    /// used, this field contains the media type of this document,
    /// which differs from the descriptor use of mediaType.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[getset(get = "pub", set = "pub")]
    #[builder(default)]
    media_type: Option<MediaType>,
    /// This OPTIONAL property contains the type of an artifact when the manifest is used for an
    /// artifact. If defined, the value MUST comply with RFC 6838, including the naming
    /// requirements in its section 4.2, and MAY be registered with IANA.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[getset(get = "pub", set = "pub")]
    #[builder(default)]
    artifact_type: Option<MediaType>,
    /// This REQUIRED property contains a list of manifests for specific
    /// platforms. While this property MUST be present, the size of
    /// the array MAY be zero.
    #[getset(get_mut = "pub", get = "pub", set = "pub")]
    manifests: Vec<Descriptor>,
    /// This OPTIONAL property specifies a descriptor of another manifest. This value, used by the
    /// referrers API, indicates a relationship to the specified manifest.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[getset(get = "pub", set = "pub")]
    #[builder(default)]
    subject: Option<Descriptor>,
    /// This OPTIONAL property contains arbitrary metadata for the image
    /// index. This OPTIONAL property MUST use the annotation rules.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[getset(get_mut = "pub", get = "pub", set = "pub")]
    #[builder(default)]
    annotations: Option<HashMap<String, String>>,
}

impl ImageIndex {
    /// Attempts to load an image index from a file.
    /// # Errors
    /// This function will return an [OciSpecError::Io](crate::OciSpecError::Io)
    /// if the file does not exist or an
    /// [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the image index
    /// cannot be deserialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::ImageIndex;
    ///
    /// let image_index = ImageIndex::from_file("index.json").unwrap();
    /// ```
    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<ImageIndex> {
        from_file(path)
    }

    /// Attempts to load an image index from a stream.
    /// # Errors
    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe)
    /// if the index cannot be deserialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::ImageIndex;
    /// use std::fs::File;
    ///
    /// let reader = File::open("index.json").unwrap();
    /// let image_index = ImageIndex::from_reader(reader).unwrap();
    /// ```
    pub fn from_reader<R: Read>(reader: R) -> Result<ImageIndex> {
        from_reader(reader)
    }

    /// Attempts to write an image index to a file as JSON. If the file already exists, it
    /// will be overwritten.
    /// # Errors
    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if
    /// the image index cannot be serialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::ImageIndex;
    ///
    /// let image_index = ImageIndex::from_file("index.json").unwrap();
    /// image_index.to_file("my-index.json").unwrap();
    /// ```
    pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
        to_file(&self, path, false)
    }

    /// Attempts to write an image index to a file as pretty printed JSON. If the file
    /// already exists, it will be overwritten.
    /// # Errors
    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if
    /// the image index cannot be serialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::ImageIndex;
    ///
    /// let image_index = ImageIndex::from_file("index.json").unwrap();
    /// image_index.to_file_pretty("my-index.json").unwrap();
    /// ```
    pub fn to_file_pretty<P: AsRef<Path>>(&self, path: P) -> Result<()> {
        to_file(&self, path, true)
    }

    /// Attempts to write an image index to a stream as JSON.
    /// # Errors
    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if
    /// the image index cannot be serialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::ImageIndex;
    ///
    /// let image_index = ImageIndex::from_file("index.json").unwrap();
    /// let mut writer = Vec::new();
    /// image_index.to_writer(&mut writer);
    /// ```
    pub fn to_writer<W: Write>(&self, writer: &mut W) -> Result<()> {
        to_writer(&self, writer, false)
    }

    /// Attempts to write an image index to a stream as pretty printed JSON.
    /// # Errors
    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if
    /// the image index cannot be serialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::ImageIndex;
    ///
    /// let image_index = ImageIndex::from_file("index.json").unwrap();
    /// let mut writer = Vec::new();
    /// image_index.to_writer_pretty(&mut writer);
    /// ```
    pub fn to_writer_pretty<W: Write>(&self, writer: &mut W) -> Result<()> {
        to_writer(&self, writer, true)
    }

    /// Attempts to write an image index to a string as JSON.
    /// # Errors
    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if
    /// the image configuration cannot be serialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::ImageIndex;
    ///
    /// let image_index = ImageIndex::from_file("index.json").unwrap();
    /// let json_str = image_index.to_string().unwrap();
    /// ```
    pub fn to_string(&self) -> Result<String> {
        to_string(&self, false)
    }

    /// Attempts to write an image index to a string as pretty printed JSON.
    /// # Errors
    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if
    /// the image configuration cannot be serialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::ImageIndex;
    ///
    /// let image_index = ImageIndex::from_file("index.json").unwrap();
    /// let json_str = image_index.to_string_pretty().unwrap();
    /// ```
    pub fn to_string_pretty(&self) -> Result<String> {
        to_string(&self, true)
    }
}

impl Default for ImageIndex {
    fn default() -> Self {
        Self {
            schema_version: SCHEMA_VERSION,
            media_type: Default::default(),
            manifests: Default::default(),
            annotations: Default::default(),
            artifact_type: Default::default(),
            subject: Default::default(),
        }
    }
}

/// This ToString trait is automatically implemented for any type which implements the Display trait.
/// As such, ToString shouldn’t be implemented directly: Display should be implemented instead,
/// and you get the ToString implementation for free.
impl Display for ImageIndex {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        // Serde serialization never fails since this is
        // a combination of String and enums.
        write!(
            f,
            "{}",
            self.to_string_pretty()
                .expect("ImageIndex to JSON conversion failed")
        )
    }
}

#[cfg(test)]
mod tests {
    use std::str::FromStr;
    use std::{fs, path::PathBuf};

    use super::*;
    use crate::image::{Arch, Os, Sha256Digest};
    use crate::image::{DescriptorBuilder, PlatformBuilder};

    fn create_index() -> ImageIndex {
        let ppc_manifest = DescriptorBuilder::default()
            .media_type(MediaType::ImageManifest)
            .digest(
                Sha256Digest::from_str(
                    "e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f",
                )
                .unwrap(),
            )
            .size(7143u64)
            .platform(
                PlatformBuilder::default()
                    .architecture(Arch::PowerPC64le)
                    .os(Os::Linux)
                    .build()
                    .expect("build ppc64le platform"),
            )
            .build()
            .expect("build ppc manifest descriptor");

        let amd64_manifest = DescriptorBuilder::default()
            .media_type(MediaType::ImageManifest)
            .digest(
                Sha256Digest::from_str(
                    "5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
                )
                .unwrap(),
            )
            .size(7682u64)
            .platform(
                PlatformBuilder::default()
                    .architecture(Arch::Amd64)
                    .os(Os::Linux)
                    .build()
                    .expect("build amd64 platform"),
            )
            .build()
            .expect("build amd64 manifest descriptor");

        ImageIndexBuilder::default()
            .schema_version(SCHEMA_VERSION)
            .manifests(vec![ppc_manifest, amd64_manifest])
            .build()
            .expect("build image index")
    }

    fn get_index_path() -> PathBuf {
        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test/data/index.json")
    }

    #[test]
    fn load_index_from_file() {
        // arrange
        let index_path = get_index_path();

        // act
        let actual = ImageIndex::from_file(index_path).expect("from file");

        // assert
        let expected = create_index();
        assert_eq!(actual, expected);
    }

    #[test]
    fn load_index_from_reader() {
        // arrange
        let reader = fs::read(get_index_path()).expect("read index");

        // act
        let actual = ImageIndex::from_reader(&*reader).expect("from reader");

        // assert
        let expected = create_index();
        assert_eq!(actual, expected);
    }

    #[test]
    fn save_index_to_file() {
        // arrange
        let tmp = std::env::temp_dir().join("save_index_to_file");
        fs::create_dir_all(&tmp).expect("create test directory");
        let index = create_index();
        let index_path = tmp.join("index.json");

        // act
        index
            .to_file_pretty(&index_path)
            .expect("write index to file");

        // assert
        let actual = fs::read_to_string(index_path).expect("read actual");
        let expected = fs::read_to_string(get_index_path()).expect("read expected");
        assert_eq!(actual, expected);
    }

    #[test]
    fn save_index_to_writer() {
        // arrange
        let mut actual = Vec::new();
        let index = create_index();

        // act
        index.to_writer_pretty(&mut actual).expect("to writer");

        // assert
        let expected = fs::read(get_index_path()).expect("read expected");
        assert_eq!(actual, expected);
    }

    #[test]
    fn save_index_to_string() {
        // arrange
        let index = create_index();

        // act
        let actual = index.to_string_pretty().expect("to string");

        // assert
        let expected = fs::read_to_string(get_index_path()).expect("read expected");
        assert_eq!(actual, expected);
    }
}


================================================
FILE: src/image/manifest.rs
================================================
use super::{Descriptor, MediaType};
use crate::{
    error::{OciSpecError, Result},
    from_file, from_reader, to_file, to_string, to_writer,
};
use derive_builder::Builder;
use getset::{CopyGetters, Getters, MutGetters, Setters};
use serde::{Deserialize, Serialize};
use std::{
    collections::HashMap,
    fmt::Display,
    io::{Read, Write},
    path::Path,
};

#[derive(
    Builder,
    Clone,
    CopyGetters,
    Debug,
    Deserialize,
    Eq,
    Getters,
    MutGetters,
    Setters,
    PartialEq,
    Serialize,
)]
#[serde(rename_all = "camelCase")]
#[builder(
    pattern = "owned",
    setter(into, strip_option),
    build_fn(error = "OciSpecError")
)]
/// Unlike the image index, which contains information about a set of images
/// that can span a variety of architectures and operating systems, an image
/// manifest provides a configuration and set of layers for a single
/// container image for a specific architecture and operating system.
pub struct ImageManifest {
    /// This REQUIRED property specifies the image manifest schema version.
    /// For this version of the specification, this MUST be 2 to ensure
    /// backward compatibility with older versions of Docker. The
    /// value of this field will not change. This field MAY be
    /// removed in a future version of the specification.
    #[getset(get_copy = "pub", set = "pub")]
    schema_version: u32,
    /// This property is reserved for use, to maintain compatibility. When
    /// used, this field contains the media type of this document,
    /// which differs from the descriptor use of mediaType.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[getset(get = "pub", set = "pub")]
    #[builder(default)]
    media_type: Option<MediaType>,
    /// This OPTIONAL property contains the type of an artifact when the manifest is used for an
    /// artifact. This MUST be set when config.mediaType is set to the empty value. If defined, the
    /// value MUST comply with RFC 6838, including the naming requirements in its section 4.2, and
    /// MAY be registered with IANA. Implementations storing or copying image manifests MUST NOT
    /// error on encountering an artifactType that is unknown to the implementation.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[getset(get = "pub", set = "pub")]
    #[builder(default)]
    artifact_type: Option<MediaType>,
    /// This REQUIRED property references a configuration object for a
    /// container, by digest. Beyond the descriptor requirements,
    /// the value has the following additional restrictions:
    /// The media type descriptor property has additional restrictions for
    /// config. Implementations MUST support at least the following
    /// media types:
    /// - application/vnd.oci.image.config.v1+json
    ///
    /// Manifests concerned with portability SHOULD use one of the above
    /// media types.
    #[getset(get = "pub", set = "pub")]
    config: Descriptor,
    /// Each item in the array MUST be a descriptor. The array MUST have the
    /// base layer at index 0. Subsequent layers MUST then follow in
    /// stack order (i.e. from `layers[0]` to `layers[len(layers)-1]`).
    /// The final filesystem layout MUST match the result of applying
    /// the layers to an empty directory. The ownership, mode, and other
    /// attributes of the initial empty directory are unspecified.
    #[getset(get_mut = "pub", get = "pub", set = "pub")]
    layers: Vec<Descriptor>,
    /// This OPTIONAL property specifies a descriptor of another manifest. This value, used by the
    /// referrers API, indicates a relationship to the specified manifest.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[getset(get = "pub", set = "pub")]
    #[builder(default)]
    subject: Option<Descriptor>,
    /// This OPTIONAL property contains arbitrary metadata for the image
    /// manifest. This OPTIONAL property MUST use the annotation
    /// rules.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[getset(get_mut = "pub", get = "pub", set = "pub")]
    #[builder(default)]
    annotations: Option<HashMap<String, String>>,
}

impl ImageManifest {
    /// Attempts to load an image manifest from a file.
    /// # Errors
    /// This function will return an [OciSpecError::Io](crate::OciSpecError::Io)
    /// if the file does not exist or an
    /// [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the image manifest
    /// cannot be deserialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::ImageManifest;
    ///
    /// let image_manifest = ImageManifest::from_file("manifest.json").unwrap();
    /// ```
    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<ImageManifest> {
        from_file(path)
    }

    /// Attempts to load an image manifest from a stream.
    /// # Errors
    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe)
    /// if the manifest cannot be deserialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::ImageManifest;
    /// use std::fs::File;
    ///
    /// let reader = File::open("manifest.json").unwrap();
    /// let image_manifest = ImageManifest::from_reader(reader).unwrap();
    /// ```
    pub fn from_reader<R: Read>(reader: R) -> Result<ImageManifest> {
        from_reader(reader)
    }

    /// Attempts to write an image manifest to a file as JSON. If the file already exists, it
    /// will be overwritten.
    /// # Errors
    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if
    /// the image manifest cannot be serialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::ImageManifest;
    ///
    /// let image_manifest = ImageManifest::from_file("manifest.json").unwrap();
    /// image_manifest.to_file("my-manifest.json").unwrap();
    /// ```
    pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
        to_file(&self, path, false)
    }

    /// Attempts to write an image manifest to a file as pretty printed JSON. If the file already exists, it
    /// will be overwritten.
    /// # Errors
    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if
    /// the image manifest cannot be serialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::ImageManifest;
    ///
    /// let image_manifest = ImageManifest::from_file("manifest.json").unwrap();
    /// image_manifest.to_file_pretty("my-manifest.json").unwrap();
    /// ```
    pub fn to_file_pretty<P: AsRef<Path>>(&self, path: P) -> Result<()> {
        to_file(&self, path, true)
    }

    /// Attempts to write an image manifest to a stream as JSON.
    /// # Errors
    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if
    /// the image manifest cannot be serialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::ImageManifest;
    ///
    /// let image_manifest = ImageManifest::from_file("manifest.json").unwrap();
    /// let mut writer = Vec::new();
    /// image_manifest.to_writer(&mut writer);
    /// ```
    pub fn to_writer<W: Write>(&self, writer: &mut W) -> Result<()> {
        to_writer(&self, writer, false)
    }

    /// Attempts to write an image manifest to a stream as pretty printed JSON.
    /// # Errors
    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if
    /// the image manifest cannot be serialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::ImageManifest;
    ///
    /// let image_manifest = ImageManifest::from_file("manifest.json").unwrap();
    /// let mut writer = Vec::new();
    /// image_manifest.to_writer_pretty(&mut writer);
    /// ```
    pub fn to_writer_pretty<W: Write>(&self, writer: &mut W) -> Result<()> {
        to_writer(&self, writer, true)
    }

    /// Attempts to write an image manifest to a string as JSON.
    /// # Errors
    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if
    /// the image configuration cannot be serialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::ImageManifest;
    ///
    /// let image_manifest = ImageManifest::from_file("manifest.json").unwrap();
    /// let json_str = image_manifest.to_string().unwrap();
    /// ```
    pub fn to_string(&self) -> Result<String> {
        to_string(&self, false)
    }

    /// Attempts to write an image manifest to a string as pretty printed JSON.
    /// # Errors
    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if
    /// the image configuration cannot be serialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::ImageManifest;
    ///
    /// let image_manifest = ImageManifest::from_file("manifest.json").unwrap();
    /// let json_str = image_manifest.to_string_pretty().unwrap();
    /// ```
    pub fn to_string_pretty(&self) -> Result<String> {
        to_string(&self, true)
    }
}

/// This ToString trait is automatically implemented for any type which implements the Display trait.
/// As such, ToString shouldn’t be implemented directly: Display should be implemented instead,
/// and you get the ToString implementation for free.
impl Display for ImageManifest {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        // Serde serialization never fails since this is
        // a combination of String and enums.
        write!(
            f,
            "{}",
            self.to_string_pretty()
                .expect("ImageManifest to JSON conversion failed")
        )
    }
}

#[cfg(test)]
mod tests {
    use std::{fs, path::PathBuf, str::FromStr};

    use super::*;
    use crate::image::{DescriptorBuilder, Sha256Digest};

    fn create_manifest() -> ImageManifest {
        use crate::image::SCHEMA_VERSION;

        let config = DescriptorBuilder::default()
            .media_type(MediaType::ImageConfig)
            .size(7023u64)
            .digest(
                "b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7"
                    .parse::<Sha256Digest>()
                    .unwrap(),
            )
            .build()
            .expect("build config descriptor");

        let layers: Vec<Descriptor> = [
            (
                32654u64,
                "9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0",
            ),
            (
                16724,
                "3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b",
            ),
            (
                73109,
                "ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736",
            ),
        ]
        .iter()
        .map(|l| {
            DescriptorBuilder::default()
                .media_type(MediaType::ImageLayerGzip)
                .size(l.0)
                .digest(Sha256Digest::from_str(l.1).unwrap())
                .build()
                .expect("build layer")
        })
        .collect();

        ImageManifestBuilder::default()
            .schema_version(SCHEMA_VERSION)
            .config(config)
            .layers(layers)
            .build()
            .expect("build image manifest")
    }

    fn get_manifest_path() -> PathBuf {
        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test/data/manifest.json")
    }

    #[test]
    fn load_manifest_from_file() {
        // arrange
        let manifest_path = get_manifest_path();
        let expected = create_manifest();

        // act
        let actual = ImageManifest::from_file(manifest_path).expect("from file");

        // assert
        assert_eq!(actual, expected);
    }

    #[test]
    fn getset() {
        let mut manifest = create_manifest();
        assert_eq!(manifest.layers().len(), 3);
        let layer_copy = manifest.layers()[0].clone();
        manifest.layers_mut().push(layer_copy);
        assert_eq!(manifest.layers().len(), 4);
    }

    #[test]
    fn load_manifest_from_reader() {
        // arrange
        let reader = fs::read(get_manifest_path()).expect("read manifest");

        // act
        let actual = ImageManifest::from_reader(&*reader).expect("from reader");

        // assert
        let expected = create_manifest();
        assert_eq!(actual, expected);
    }

    #[test]
    fn save_manifest_to_file() {
        // arrange
        let tmp = std::env::temp_dir().join("save_manifest_to_file");
        fs::create_dir_all(&tmp).expect("create test directory");
        let manifest = create_manifest();
        let manifest_path = tmp.join("manifest.json");

        // act
        manifest
            .to_file_pretty(&manifest_path)
            .expect("write manifest to file");

        // assert
        let actual = fs::read_to_string(manifest_path).expect("read actual");
        let expected = fs::read_to_string(get_manifest_path()).expect("read expected");
        assert_eq!(actual, expected);
    }

    #[test]
    fn save_manifest_to_writer() {
        // arrange
        let manifest = create_manifest();
        let mut actual = Vec::new();

        // act
        manifest.to_writer_pretty(&mut actual).expect("to writer");

        // assert
        let expected = fs::read(get_manifest_path()).expect("read expected");
        assert_eq!(actual, expected);
    }

    #[test]
    fn save_manifest_to_string() {
        // arrange
        let manifest = create_manifest();

        // act
        let actual = manifest.to_string_pretty().expect("to string");

        // assert
        let expected = fs::read_to_string(get_manifest_path()).expect("read expected");
        assert_eq!(actual, expected);
    }
}


================================================
FILE: src/image/mod.rs
================================================
//! [OCI image spec](https://github.com/opencontainers/image-spec) types and definitions.

mod annotations;
mod artifact;
mod config;
mod descriptor;
mod digest;
mod index;
mod manifest;
mod oci_layout;
mod version;

use std::fmt::Display;

use serde::{Deserialize, Serialize};

pub use annotations::*;
pub use artifact::*;
pub use config::*;
pub use descriptor::*;
pub use digest::*;
pub use index::*;
pub use manifest::*;
pub use oci_layout::*;
pub use version::*;

/// Media types used by OCI image format spec. Values MUST comply with RFC 6838,
/// including the naming requirements in its section 4.2.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum MediaType {
    /// MediaType Descriptor specifies the media type for a content descriptor.
    Descriptor,
    /// MediaType LayoutHeader specifies the media type for the oci-layout.
    LayoutHeader,
    /// MediaType ImageManifest specifies the media type for an image manifest.
    ImageManifest,
    /// MediaType ImageIndex specifies the media type for an image index.
    ImageIndex,
    /// MediaType ImageLayer is the media type used for layers referenced by the
    /// manifest.
    ImageLayer,
    /// MediaType ImageLayerGzip is the media type used for gzipped layers
    /// referenced by the manifest.
    ImageLayerGzip,
    /// MediaType ImageLayerZstd is the media type used for zstd compressed
    /// layers referenced by the manifest.
    ImageLayerZstd,
    /// MediaType ImageLayerNonDistributable is the media type for layers
    /// referenced by the manifest but with distribution restrictions.
    ImageLayerNonDistributable,
    /// MediaType ImageLayerNonDistributableGzip is the media type for
    /// gzipped layers referenced by the manifest but with distribution
    /// restrictions.
    ImageLayerNonDistributableGzip,
    /// MediaType ImageLayerNonDistributableZstd is the media type for zstd
    /// compressed layers referenced by the manifest but with distribution
    /// restrictions.
    ImageLayerNonDistributableZstd,
    /// MediaType ImageConfig specifies the media type for the image
    /// configuration.
    ImageConfig,
    /// MediaType ArtifactManifest specifies the media type used for content addressable
    /// artifacts to store them along side container images in a registry.
    ArtifactManifest,
    /// MediaType EmptyJSON specifies a descriptor that has no content for the implementation. The
    /// blob payload is the most minimal content that is still a valid JSON object: {} (size of 2).
    /// The blob digest of {} is
    /// sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a.
    EmptyJSON,
    /// MediaType not specified by OCI image format.
    Other(String),
}

impl Display for MediaType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.as_ref())
    }
}

impl From<&str> for MediaType {
    fn from(media_type: &str) -> Self {
        match media_type {
            "application/vnd.oci.descriptor" => MediaType::Descriptor,
            "application/vnd.oci.layout.header.v1+json" => MediaType::LayoutHeader,
            "application/vnd.oci.image.manifest.v1+json" => MediaType::ImageManifest,
            "application/vnd.oci.image.index.v1+json" => MediaType::ImageIndex,
            "application/vnd.oci.image.layer.v1.tar" => MediaType::ImageLayer,
            "application/vnd.oci.image.layer.v1.tar+gzip" => MediaType::ImageLayerGzip,
            "application/vnd.oci.image.layer.v1.tar+zstd" => MediaType::ImageLayerZstd,
            "application/vnd.oci.image.layer.nondistributable.v1.tar" => {
                MediaType::ImageLayerNonDistributable
            }
            "application/vnd.oci.image.layer.nondistributable.v1.tar+gzip" => {
                MediaType::ImageLayerNonDistributableGzip
            }
            "application/vnd.oci.image.layer.nondistributable.v1.tar+zstd" => {
                MediaType::ImageLayerNonDistributableZstd
            }
            "application/vnd.oci.image.config.v1+json" => MediaType::ImageConfig,
            "application/vnd.oci.artifact.manifest.v1+json" => MediaType::ArtifactManifest,
            "application/vnd.oci.empty.v1+json" => MediaType::EmptyJSON,
            media => MediaType::Other(media.to_owned()),
        }
    }
}

impl From<MediaType> for String {
    fn from(media_type: MediaType) -> Self {
        media_type.as_ref().to_owned()
    }
}

impl AsRef<str> for MediaType {
    fn as_ref(&self) -> &str {
        match self {
            Self::Descriptor => "application/vnd.oci.descriptor",
            Self::LayoutHeader => "application/vnd.oci.layout.header.v1+json",
            Self::ImageManifest => "application/vnd.oci.image.manifest.v1+json",
            Self::ImageIndex => "application/vnd.oci.image.index.v1+json",
            Self::ImageLayer => "application/vnd.oci.image.layer.v1.tar",
            Self::ImageLayerGzip => "application/vnd.oci.image.layer.v1.tar+gzip",
            Self::ImageLayerZstd => "application/vnd.oci.image.layer.v1.tar+zstd",
            Self::ImageLayerNonDistributable => {
                "application/vnd.oci.image.layer.nondistributable.v1.tar"
            }
            Self::ImageLayerNonDistributableGzip => {
                "application/vnd.oci.image.layer.nondistributable.v1.tar+gzip"
            }
            Self::ImageLayerNonDistributableZstd => {
                "application/vnd.oci.image.layer.nondistributable.v1.tar+zstd"
            }
            Self::ImageConfig => "application/vnd.oci.image.config.v1+json",
            Self::ArtifactManifest => "application/vnd.oci.artifact.manifest.v1+json",
            Self::EmptyJSON => "application/vnd.oci.empty.v1+json",
            Self::Other(media_type) => media_type.as_str(),
        }
    }
}

/// Trait to get the Docker Image Manifest V2 Schema 2 media type for an OCI media type
///
/// This may be necessary for compatibility with tools that do not recognize the OCI media types.
/// Where a [`MediaType`] is expected you can use `MediaType::ImageManifest.to_docker_v2s2()?` instead and
/// `impl From<&str> for MediaType` will create a [`MediaType::Other`] for it.
///
/// Not all OCI Media Types have an equivalent Docker V2S2 Media Type. In those cases, `to_docker_v2s2` will error.
pub trait ToDockerV2S2 {
    /// Get the [Docker Image Manifest V2 Schema 2](https://docs.docker.com/registry/spec/manifest-v2-2/)
    /// media type equivalent for an OCI media type
    fn to_docker_v2s2(&self) -> Result<&str, std::fmt::Error>;
}

impl ToDockerV2S2 for MediaType {
    fn to_docker_v2s2(&self) -> Result<&str, std::fmt::Error> {
        Ok(match self {
            Self::ImageIndex => "application/vnd.docker.distribution.manifest.list.v2+json",
            Self::ImageManifest => "application/vnd.docker.distribution.manifest.v2+json",
            Self::ImageConfig => "application/vnd.docker.container.image.v1+json",
            Self::ImageLayerGzip => "application/vnd.docker.image.rootfs.diff.tar.gzip",
            _ => return Err(std::fmt::Error),
        })
    }
}

impl Serialize for MediaType {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let media_type = format!("{self}");
        media_type.serialize(serializer)
    }
}

impl<'de> Deserialize<'de> for MediaType {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let media_type = String::deserialize(deserializer)?;
        Ok(media_type.as_str().into())
    }
}

/// Name of the target operating system.
#[allow(missing_docs)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Os {
    AIX,
    Android,
    Darwin,
    DragonFlyBSD,
    FreeBSD,
    Hurd,
    Illumos,
    #[allow(non_camel_case_types)]
    iOS,
    Js,
    Linux,
    Nacl,
    NetBSD,
    OpenBSD,
    Plan9,
    Solaris,
    Windows,
    #[allow(non_camel_case_types)]
    zOS,
    Other(String),
}

impl From<&str> for Os {
    fn from(os: &str) -> Self {
        match os {
            "aix" => Os::AIX,
            "android" => Os::Android,
            "darwin" => Os::Darwin,
            "dragonfly" => Os::DragonFlyBSD,
            "freebsd" => Os::FreeBSD,
            "hurd" => Os::Hurd,
            "illumos" => Os::Illumos,
            "ios" => Os::iOS,
            "js" => Os::Js,
            "linux" => Os::Linux,
            "nacl" => Os::Nacl,
            "netbsd" => Os::NetBSD,
            "openbsd" => Os::OpenBSD,
            "plan9" => Os::Plan9,
            "solaris" => Os::Solaris,
            "windows" => Os::Windows,
            "zos" => Os::zOS,
            name => Os::Other(name.to_owned()),
        }
    }
}

impl Display for Os {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let print = match self {
            Os::AIX => "aix",
            Os::Android => "android",
            Os::Darwin => "darwin",
            Os::DragonFlyBSD => "dragonfly",
            Os::FreeBSD => "freebsd",
            Os::Hurd => "hurd",
            Os::Illumos => "illumos",
            Os::iOS => "ios",
            Os::Js => "js",
            Os::Linux => "linux",
            Os::Nacl => "nacl",
            Os::NetBSD => "netbsd",
            Os::OpenBSD => "openbsd",
            Os::Plan9 => "plan9",
            Os::Solaris => "solaris",
            Os::Windows => "windows",
            Os::zOS => "zos",
            Os::Other(name) => name,
        };

        write!(f, "{print}")
    }
}

impl Serialize for Os {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let os = format!("{self}");
        os.serialize(serializer)
    }
}

impl<'de> Deserialize<'de> for Os {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let os = String::deserialize(deserializer)?;
        Ok(os.as_str().into())
    }
}

impl Default for Os {
    fn default() -> Self {
        Os::from(std::env::consts::OS)
    }
}

/// Name of the CPU target architecture.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Arch {
    /// 32 bit x86, little-endian
    #[allow(non_camel_case_types)]
    i386,
    /// 64 bit x86, little-endian
    Amd64,
    /// 64 bit x86 with 32 bit pointers, little-endian
    Amd64p32,
    /// 32 bit ARM, little-endian
    ARM,
    /// 32 bit ARM, big-endian
    ARMbe,
    /// 64 bit ARM, little-endian
    ARM64,
    /// 64 bit ARM, big-endian
    ARM64be,
    /// 64 bit Loongson RISC CPU, little-endian
    LoongArch64,
    /// 32 bit Mips, big-endian
    Mips,
    /// 32 bit Mips, little-endian
    Mipsle,
    /// 64 bit Mips, big-endian
    Mips64,
    /// 64 bit Mips, little-endian
    Mips64le,
    /// 64 bit Mips with 32 bit pointers, big-endian
    Mips64p32,
    /// 64 bit Mips with 32 bit pointers, little-endian
    Mips64p32le,
    /// 32 bit PowerPC, big endian
    PowerPC,
    /// 64 bit PowerPC, big-endian
    PowerPC64,
    /// 64 bit PowerPC, little-endian
    PowerPC64le,
    /// 32 bit RISC-V, little-endian
    RISCV,
    /// 64 bit RISC-V, little-endian
    RISCV64,
    /// 32 bit IBM System/390, big-endian
    #[allow(non_camel_case_types)]
    s390,
    /// 64 bit IBM System/390, big-endian
    #[allow(non_camel_case_types)]
    s390x,
    /// 32 bit SPARC, big-endian
    SPARC,
    /// 64 bit SPARC, bi-endian
    SPARC64,
    /// 32 bit Web Assembly
    Wasm,
    /// Architecture not specified by OCI image format
    Other(String),
}

impl Display for Arch {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let print = match self {
            Arch::i386 => "386",
            Arch::Amd64 => "amd64",
            Arch::Amd64p32 => "amd64p32",
            Arch::ARM => "arm",
            Arch::ARMbe => "armbe",
            Arch::ARM64 => "arm64",
            Arch::ARM64be => "arm64be",
            Arch::LoongArch64 => "loong64",
            Arch::Mips => "mips",
            Arch::Mipsle => "mipsle",
            Arch::Mips64 => "mips64",
            Arch::Mips64le => "mips64le",
            Arch::Mips64p32 => "mips64p32",
            Arch::Mips64p32le => "mips64p32le",
            Arch::PowerPC => "ppc",
            Arch::PowerPC64 => "ppc64",
            Arch::PowerPC64le => "ppc64le",
            Arch::RISCV => "riscv",
            Arch::RISCV64 => "riscv64",
            Arch::s390 => "s390",
            Arch::s390x => "s390x",
            Arch::SPARC => "sparc",
            Arch::SPARC64 => "sparc64",
            Arch::Wasm => "wasm",
            Arch::Other(arch) => arch,
        };

        write!(f, "{print}")
    }
}

impl From<&str> for Arch {
    fn from(arch: &str) -> Self {
        match arch {
            "386" => Arch::i386,
            "amd64" => Arch::Amd64,
            "amd64p32" => Arch::Amd64p32,
            "arm" => Arch::ARM,
            "armbe" => Arch::ARM64be,
            "arm64" => Arch::ARM64,
            "arm64be" => Arch::ARM64be,
            "loong64" => Arch::LoongArch64,
            "mips" => Arch::Mips,
            "mipsle" => Arch::Mipsle,
            "mips64" => Arch::Mips64,
            "mips64le" => Arch::Mips64le,
            "mips64p32" => Arch::Mips64p32,
            "mips64p32le" => Arch::Mips64p32le,
            "ppc" => Arch::PowerPC,
            "ppc64" => Arch::PowerPC64,
            "ppc64le" => Arch::PowerPC64le,
            "riscv" => Arch::RISCV,
            "riscv64" => Arch::RISCV64,
            "s390" => Arch::s390,
            "s390x" => Arch::s390x,
            "sparc" => Arch::SPARC,
            "sparc64" => Arch::SPARC64,
            "wasm" => Arch::Wasm,
            arch => Arch::Other(arch.to_owned()),
        }
    }
}

impl Serialize for Arch {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let arch = format!("{self}");
        arch.serialize(serializer)
    }
}

impl<'de> Deserialize<'de> for Arch {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let arch = String::deserialize(deserializer)?;
        Ok(arch.as_str().into())
    }
}

impl Default for Arch {
    fn default() -> Self {
        // Translate from the Rust architecture names to the Go versions.
        // It seems like the Rust ones are the same GNU/Linux...except for `powerpc64` and not `ppc64le`?
        // This list just contains exceptions, everything else is passed through literally.
        // See also https://github.com/containerd/containerd/blob/140ecc9247386d3be21616fe285021c081f4ea08/platforms/database.go
        let goarch = match std::env::consts::ARCH {
            "x86_64" => "amd64",
            "aarch64" => "arm64",
            "powerpc64" if cfg!(target_endian = "big") => "ppc64",
            "powerpc64" if cfg!(target_endian = "little") => "ppc64le",
            o => o,
        };
        Arch::from(goarch)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_arch_translation() {
        let a = Arch::default();
        // If you hit this, please update the mapping above.
        if let Arch::Other(o) = a {
            panic!("Architecture {o} not mapped between Rust and OCI")
        }
    }

    #[test]
    fn test_asref() {
        // This just spot checks a few conversions
        assert_eq!(
            MediaType::ImageConfig.as_ref(),
            "application/vnd.oci.image.config.v1+json"
        );
        assert_eq!(
            String::from(MediaType::ImageConfig).as_str(),
            "application/vnd.oci.image.config.v1+json"
        );
    }
}


================================================
FILE: src/image/oci_layout.rs
================================================
use crate::{
    error::{OciSpecError, Result},
    from_file, from_reader, to_file, to_string, to_writer,
};
use derive_builder::Builder;
use getset::{Getters, Setters};
use serde::{Deserialize, Serialize};
use std::{
    io::{Read, Write},
    path::Path,
};

#[derive(Builder, Clone, Debug, Deserialize, Eq, Getters, Setters, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
#[builder(
    pattern = "owned",
    setter(into, strip_option),
    build_fn(error = "OciSpecError")
)]
/// The oci layout JSON object serves as a marker for the base of an Open Container Image Layout
/// and to provide the version of the image-layout in use. The imageLayoutVersion value will align
/// with the OCI Image Specification version at the time changes to the layout are made, and will
/// pin a given version until changes to the image layout are required.
pub struct OciLayout {
    /// This REQUIRED property specifies the image layout version.
    #[getset(get = "pub", set = "pub")]
    image_layout_version: String,
}

impl OciLayout {
    /// Attempts to load an oci layout from a file.
    /// # Errors
    /// This function will return an [OciSpecError::Io](crate::OciSpecError::Io)
    /// if the file does not exist or an
    /// [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the oci layout
    /// cannot be deserialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::OciLayout;
    ///
    /// let oci_layout = OciLayout::from_file("oci-layout").unwrap();
    /// ```
    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<OciLayout> {
        from_file(path)
    }

    /// Attempts to load an oci layout from a stream.
    /// # Errors
    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe)
    /// if the oci layout cannot be deserialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::OciLayout;
    /// use std::fs::File;
    ///
    /// let reader = File::open("oci-layout").unwrap();
    /// let oci_layout = OciLayout::from_reader(reader).unwrap();
    /// ```
    pub fn from_reader<R: Read>(reader: R) -> Result<OciLayout> {
        from_reader(reader)
    }

    /// Attempts to write an oci layout to a file as JSON. If the file already exists, it
    /// will be overwritten.
    /// # Errors
    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if
    /// the oci layout cannot be serialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::OciLayout;
    ///
    /// let oci_layout = OciLayout::from_file("oci-layout").unwrap();
    /// oci_layout.to_file("oci-layout").unwrap();
    /// ```
    pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
        to_file(&self, path, false)
    }

    /// Attempts to write an oci layout to a file as pretty printed JSON. If the file
    /// already exists, it will be overwritten.
    /// # Errors
    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if
    /// the oci layout cannot be serialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::OciLayout;
    ///
    /// let oci_layout = OciLayout::from_file("oci-layout").unwrap();
    /// oci_layout.to_file_pretty("my-oci-layout").unwrap();
    /// ```
    pub fn to_file_pretty<P: AsRef<Path>>(&self, path: P) -> Result<()> {
        to_file(&self, path, true)
    }

    /// Attempts to write an oci layout to a stream as JSON.
    /// # Errors
    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if
    /// the oci layout cannot be serialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::OciLayout;
    ///
    /// let oci_layout = OciLayout::from_file("oci-layout").unwrap();
    /// let mut writer = Vec::new();
    /// oci_layout.to_writer(&mut writer);
    /// ```
    pub fn to_writer<W: Write>(&self, writer: &mut W) -> Result<()> {
        to_writer(&self, writer, false)
    }

    /// Attempts to write an oci layout to a stream as pretty printed JSON.
    /// # Errors
    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if
    /// the oci layout cannot be serialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::OciLayout;
    ///
    /// let oci_layout = OciLayout::from_file("oci-layout").unwrap();
    /// let mut writer = Vec::new();
    /// oci_layout.to_writer_pretty(&mut writer);
    /// ```
    pub fn to_writer_pretty<W: Write>(&self, writer: &mut W) -> Result<()> {
        to_writer(&self, writer, true)
    }

    /// Attempts to write an oci layout to a string as JSON.
    /// # Errors
    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if
    /// the oci layout configuration cannot be serialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::OciLayout;
    ///
    /// let oci_layout = OciLayout::from_file("oci-layout").unwrap();
    /// let json_str = oci_layout.to_string().unwrap();
    /// ```
    pub fn to_string(&self) -> Result<String> {
        to_string(&self, false)
    }

    /// Attempts to write an oci layout to a string as pretty printed JSON.
    /// # Errors
    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if
    /// the oci layout configuration cannot be serialized.
    /// # Example
    /// ``` no_run
    /// use oci_spec::image::OciLayout;
    ///
    /// let oci_layout = OciLayout::from_file("oci-layout").unwrap();
    /// let json_str = oci_layout.to_string_pretty().unwrap();
    /// ```
    pub fn to_string_pretty(&self) -> Result<String> {
        to_string(&self, true)
    }
}

#[cfg(test)]
mod tests {
    use std::{fs, path::PathBuf};

    use super::*;

    fn create_oci_layout() -> OciLayout {
        OciLayoutBuilder::default()
            .image_layout_version("lorem ipsum")
            .build()
            .expect("build oci layout")
    }

    fn get_oci_layout_path() -> PathBuf {
        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test/data/oci-layout")
    }

    #[test]
    fn load_oci_layout_from_file() {
        // arrange
        let oci_layout_path = get_oci_layout_path();

        // act
        let actual = OciLayout::from_file(oci_layout_path).expect("from file");

        // assert
        let expected = create_oci_layout();
        assert_eq!(actual, expected);
    }

    #[test]
    fn load_oci_layout_from_reader() {
        // arrange
        let reader = fs::read(get_oci_layout_path()).expect("read oci-layout");

        // act
        let actual = OciLayout::from_reader(&*reader).expect("from reader");

        // assert
        let expected = create_oci_layout();
        assert_eq!(actual, expected);
    }

    #[test]
    fn save_oci_layout_to_file() {
        // arrange
        let tmp = std::env::temp_dir().join("save_oci_layout_to_file");
        fs::create_dir_all(&tmp).expect("create test directory");
        let oci_layout = create_oci_layout();
        let oci_layout_path = tmp.join("oci-layout");

        // act
        oci_layout
            .to_file_pretty(&oci_layout_path)
            .expect("write oci-layout to file");

        // assert
        let actual = fs::read_to_string(oci_layout_path).expect("read actual");
        let expected = fs::read_to_string(get_oci_layout_path()).expect("read expected");
        assert_eq!(actual, expected);
    }

    #[test]
    fn save_oci_layout_to_writer() {
        // arrange
        let mut actual = Vec::new();
        let oci_layout = create_oci_layout();

        // act
        oci_layout.to_writer_pretty(&mut actual).expect("to writer");

        // assert
        let expected = fs::read(get_oci_layout_path()).expect("read expected");
        assert_eq!(actual, expected);
    }

    #[test]
    fn save_oci_layout_to_string() {
        // arrange
        let oci_layout = create_oci_layout();

        // act
        let actual = oci_layout.to_string_pretty().expect("to string");

        // assert
        let expected = fs::read_to_string(get_oci_layout_path()).expect("read expected");
        assert_eq!(actual, expected);
    }
}


================================================
FILE: src/image/version.rs
================================================
use const_format::formatcp;

/// API incompatible changes.
pub const VERSION_MAJOR: u32 = 1;

/// Changing functionality in a backwards-compatible manner
pub const VERSION_MINOR: u32 = 0;

/// Backwards-compatible bug fixes.
pub const VERSION_PATCH: u32 = 1;

/// Indicates development branch. Releases will be empty string.
pub const VERSION_DEV: &str = "-dev";

/// Retrieve the version as static str representation.
pub const VERSION: &str = formatcp!("{VERSION_MAJOR}.{VERSION_MINOR}.{VERSION_PATCH}{VERSION_DEV}");

/// Retrieve the version as string representation.
///
/// Use [`VERSION`] instead.
#[deprecated]
pub fn version() -> String {
    VERSION.to_owned()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[allow(deprecated)]
    fn version_test() {
        assert_eq!(version(), "1.0.1-dev".to_string())
    }
}


================================================
FILE: src/lib.rs
================================================
#![deny(missing_docs, warnings)]
#![doc = include_str!("../README.md")]
#![allow(clippy::too_long_first_doc_paragraph)]

#[cfg(feature = "distribution")]
pub mod distribution;
mod error;
#[cfg(feature = "image")]
pub mod image;
#[cfg(feature = "runtime")]
pub mod runtime;

use std::{
    fs::{self, OpenOptions},
    io::{Read, Write},
    path::Path,
};

use serde::{de::DeserializeOwned, Serialize};

pub use error::*;

fn from_file<P: AsRef<Path>, T: DeserializeOwned>(path: P) -> Result<T> {
    let path = path.as_ref();
    let manifest_file = std::io::BufReader::new(fs::File::open(path)?);
    let manifest = serde_json::from_reader(manifest_file)?;
    Ok(manifest)
}

fn from_reader<R: Read, T: DeserializeOwned>(reader: R) -> Result<T> {
    let manifest = serde_json::from_reader(reader)?;
    Ok(manifest)
}

fn to_file<P: AsRef<Path>, T: Serialize>(item: &T, path: P, pretty: bool) -> Result<()> {
    let path = path.as_ref();
    let file = OpenOptions::new()
        .write(true)
        .create(true)
        .truncate(true)
        .open(path)?;
    let file = std::io::BufWriter::new(file);

    match pretty {
        true => serde_json::to_writer_pretty(file, item)?,
        false => serde_json::to_writer(file, item)?,
    }

    Ok(())
}

fn to_writer<W: Write, T: Serialize>(item: &T, writer: &mut W, pretty: bool) -> Result<()> {
    match pretty {
        true => serde_json::to_writer_pretty(writer, item)?,
        false => serde_json::to_writer(writer, item)?,
    }

    Ok(())
}

fn to_string<T: Serialize>(item: &T, pretty: bool) -> Result<String> {
    Ok(match pretty {
        true => serde_json::to_string_pretty(item)?,
        false => serde_json::to_string(item)?,
    })
}

// A generic helper for any Option containing a collection whose reference implements `IntoIterator` (e.g., Vec, HashMap).
fn is_none_or_empty<C>(opt: &Option<C>) -> bool
where
    for<'a> &'a C: IntoIterator,
{
    opt.as_ref().is_none_or(|c| c.into_iter().next().is_none())
}


================================================
FILE: src/runtime/capability.rs
================================================
use serde::{
    de::{Deserializer, Error},
    Deserialize, Serialize,
};
use std::collections::HashSet;

use strum_macros::{Display, EnumString};

/// Capabilities is a unique set of Capability values.
pub type Capabilities = HashSet<Capability>;

#[derive(Clone, Copy, Debug, EnumString, Eq, Display, Hash, PartialEq, Serialize)]
/// All available capabilities.
///
/// For the purpose of performing permission checks, traditional UNIX
/// implementations distinguish two categories of processes: privileged
/// processes (whose effective user ID is 0, referred to as superuser or root),
/// and unprivileged processes (whose effective UID is nonzero). Privileged
/// processes bypass all kernel permission checks, while unprivileged processes
/// are subject to full permission checking based on the process's credentials
/// (usually: effective UID, effective GID, and supplementary group list).
///
/// Starting with kernel 2.2, Linux divides the privileges traditionally
/// associated with superuser into distinct units, known as capabilities, which
/// can be independently enabled and disabled. Capabilities are a per-thread attribute.
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
pub enum Capability {
    #[serde(rename = "CAP_AUDIT_CONTROL")]
    /// Enable and disable kernel auditing; change auditing filter rules;
    /// retrieve auditing status and filtering rules.
    ///
    /// _since Linux 2.6.11_
    AuditControl,

    #[serde(rename = "CAP_AUDIT_READ")]
    /// Allow reading the audit log via multicast netlink socket.
    ///
    /// _since Linux 3.16_
    AuditRead,

    #[serde(rename = "CAP_AUDIT_WRITE")]
    /// Write records to kernel auditing log.
    ///
    /// _since Linux 2.6.11_
    AuditWrite,

    #[serde(rename = "CAP_BLOCK_SUSPEND")]
    /// Employ features that can block system suspend
    /// ([epoll(7)](https://man7.org/linux/man-pages/man7/epoll.7.html)
    /// **EPOLLWAKEUP**, `/proc/sys/wake_lock`).
    ///
    /// _since Linux 3.5_
    BlockSuspend,

    #[serde(rename = "CAP_BPF")]
    /// Employ privileged BPF operations; see
    /// [bpf(2)](https://man7.org/linux/man-pages/man2/bpf.2.html) and
    /// [bpf-helpers(7)](https://man7.org/linux/man-pages/man7/bpf-helpers.7.html).
    ///
    /// This capability was added to separate out BPF functionality from the
    /// overloaded **CAP_SYS_ADMIN** capability.
    ///
    /// _since Linux 5.8_
    Bpf,

    #[serde(rename = "CAP_CHECKPOINT_RESTORE")]
    /// - update `/proc/sys/kernel/ns_last_pid` (see
    ///   [pid_namespaces(7)](https://man7.org/linux/man-pages/man7/pid_namespaces.7.html))
    /// - employ the set_tid feature of
    ///   [clone3(2)](https://man7.org/linux/man-pages/man2/clone3.2.html)
    /// - read the contents of the symbolic links in `/proc/[pid]/map_files` for
    ///   other processes.
    ///
    /// This capability was added to separate out BPF functionality from the
    /// overloaded **CAP_SYS_ADMIN** capability.
    ///
    /// _since Linux 5.9_
    CheckpointRestore,

    #[serde(rename = "CAP_CHOWN")]
    /// Make arbitrary changes to file UIDs and GIDs (see
    /// [chown(2)](https://man7.org/linux/man-pages/man2/chown.2.html)).
    Chown,

    #[serde(rename = "CAP_DAC_OVERRIDE")]
    /// Bypass file read, write, and execute permission checks.
    ///
    /// (DAC is an abbreviation of "discretionary access control".)
    DacOverride,

    #[serde(rename = "CAP_DAC_READ_SEARCH")]
    /// - bypass file read permission checks and directory read and execute
    ///   permission checks
    /// - invoke
    ///   [open_by_handle_at(2)](https://man7.org/linux/man-pages/man2/open_by_handle_at.2.html)
    /// - use the
    ///   [linkat(2)](https://man7.org/linux/man-pages/man2/linkat.2.html)
    ///   **AT_EMPTY_PATH** flag to create a link to a file referred to by a
    ///   file descriptor.
    DacReadSearch,

    #[serde(rename = "CAP_FOWNER")]
    /// - Bypass permission checks on operations that normally require the
    ///   filesystem UID of the process to match the UID of the file (e.g.,
    ///   [chmod(2)](https://man7.org/linux/man-pages/man2/chmod.2.html),
    ///   [utime(2)](https://man7.org/linux/man-pages/man2/utime.2.html)),
    ///   excluding those operations covered by **CAP_DAC_OVERRIDE** and
    ///   **CAP_DAC_READ_SEARCH**
    /// - set inode flags (see
    ///   [ioctl_iflags(2)](https://man7.org/linux/man-pages/man2/ioctl_iflag
Download .txt
gitextract_jzfwglfk/

├── .codecov.yml
├── .devcontainer/
│   └── devcontainer.json
├── .github/
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── dependabot.yml
│   ├── grcov.yml
│   ├── release.yml
│   └── workflows/
│       ├── ci.yml
│       ├── cross.yml
│       ├── gh-pages.yml
│       ├── release.yml
│       └── security-audit.yml
├── .gitignore
├── .rustfmt.toml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Cargo.toml
├── GOVERNANCE.md
├── LICENSE
├── MAINTAINERS.md
├── OWNERS
├── OWNERS_ALIASES
├── README.md
├── SECURITY.md
├── SECURITY_CONTACTS
├── hack/
│   └── release
├── release.md
├── src/
│   ├── distribution/
│   │   ├── error.rs
│   │   ├── mod.rs
│   │   ├── reference.rs
│   │   ├── repository.rs
│   │   ├── tag.rs
│   │   └── version.rs
│   ├── error.rs
│   ├── image/
│   │   ├── annotations.rs
│   │   ├── artifact.rs
│   │   ├── config.rs
│   │   ├── descriptor.rs
│   │   ├── digest.rs
│   │   ├── index.rs
│   │   ├── manifest.rs
│   │   ├── mod.rs
│   │   ├── oci_layout.rs
│   │   └── version.rs
│   ├── lib.rs
│   └── runtime/
│       ├── capability.rs
│       ├── features.rs
│       ├── hooks.rs
│       ├── linux.rs
│       ├── miscellaneous.rs
│       ├── mod.rs
│       ├── process.rs
│       ├── solaris.rs
│       ├── state.rs
│       ├── test/
│       │   └── fixture/
│       │       ├── sample.json
│       │       ├── sample_state.json
│       │       ├── sample_windows.json
│       │       └── sample_zos.json
│       ├── test.rs
│       ├── version.rs
│       ├── vm.rs
│       ├── windows.rs
│       └── zos.rs
└── test/
    └── data/
        ├── artifact_manifest.json
        ├── config.json
        ├── index.json
        ├── manifest.json
        └── oci-layout
Download .txt
SYMBOL INDEX (449 symbols across 31 files)

FILE: src/distribution/error.rs
  constant ERR_REGISTRY (line 12) | pub const ERR_REGISTRY: &str = "distribution: registry returned error";
  type ErrorCode (line 18) | pub enum ErrorCode {
  type ErrorResponse (line 58) | pub struct ErrorResponse {
    method detail (line 71) | pub fn detail(&self) -> &[ErrorInfo] {
  method fmt (line 64) | fn fmt(&self, f: &mut Formatter) -> fmt::Result {
  type ErrorInfo (line 84) | pub struct ErrorInfo {
  function deserialize (line 107) | pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<String>, D:...
  function serialize (line 123) | pub fn serialize<S>(target: &Option<String>, serializer: S) -> Result<S:...
  function error_response_success (line 148) | fn error_response_success() -> Result<()> {
  function error_response_failure (line 156) | fn error_response_failure() {
  function error_info_success (line 161) | fn error_info_success() -> Result<()> {
  function error_info_failure (line 172) | fn error_info_failure() {
  function error_info_serialize_success (line 177) | fn error_info_serialize_success() -> Result<()> {
  function error_info_serialize_failure (line 188) | fn error_info_serialize_failure() -> Result<()> {
  function error_info_deserialize_success (line 199) | fn error_info_deserialize_success() -> Result<()> {

FILE: src/distribution/reference.rs
  constant NAME_TOTAL_LENGTH_MAX (line 10) | const NAME_TOTAL_LENGTH_MAX: usize = 255;
  constant DOCKER_HUB_DOMAIN_LEGACY (line 12) | const DOCKER_HUB_DOMAIN_LEGACY: &str = "index.docker.io";
  constant DOCKER_HUB_DOMAIN (line 13) | const DOCKER_HUB_DOMAIN: &str = "docker.io";
  constant DOCKER_HUB_OFFICIAL_REPO_NAME (line 14) | const DOCKER_HUB_OFFICIAL_REPO_NAME: &str = "library";
  constant DEFAULT_TAG (line 15) | const DEFAULT_TAG: &str = "latest";
  constant REFERENCE_REGEXP (line 18) | const REFERENCE_REGEXP: &str = r"^((?:(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0...
  function reference_regexp (line 20) | fn reference_regexp() -> &'static Regex {
  type ParseError (line 32) | pub enum ParseError {
  type Reference (line 77) | pub struct Reference {
    method with_tag (line 90) | pub fn with_tag(registry: String, repository: String, tag: String) -> ...
    method with_digest (line 101) | pub fn with_digest(registry: String, repository: String, digest: Strin...
    method with_tag_and_digest (line 128) | pub fn with_tag_and_digest(
    method clone_with_digest (line 144) | pub fn clone_with_digest(&self, digest: String) -> Self {
    method set_mirror_registry (line 167) | pub fn set_mirror_registry(&mut self, registry: String) {
    method resolve_registry (line 177) | pub fn resolve_registry(&self) -> &str {
    method registry (line 186) | pub fn registry(&self) -> &str {
    method repository (line 191) | pub fn repository(&self) -> &str {
    method tag (line 196) | pub fn tag(&self) -> Option<&str> {
    method digest (line 201) | pub fn digest(&self) -> Option<&str> {
    method namespace (line 210) | pub fn namespace(&self) -> Option<&str> {
    method whole (line 219) | pub fn whole(&self) -> String {
    method fmt (line 225) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    type Error (line 264) | type Error = ParseError;
    method try_from (line 266) | fn try_from(s: &str) -> Result<Self, Self::Error> {
    type Error (line 321) | type Error = ParseError;
    method try_from (line 322) | fn try_from(string: String) -> Result<Self, Self::Error> {
  type Err (line 256) | type Err = ParseError;
  method from_str (line 258) | fn from_str(s: &str) -> Result<Self, Self::Err> {
  method from (line 328) | fn from(reference: Reference) -> Self {
  function split_domain (line 339) | fn split_domain(name: &str) -> (String, String) {
  function parse_good_reference (line 397) | fn parse_good_reference(
  function parse_bad_reference (line 431) | fn parse_bad_reference(input: &str, err: ParseError) {
  function test_mirror_registry (line 461) | fn test_mirror_registry(input: &str, registry: &str, resolved_registry: ...
  function test_create_reference_from_tag_and_digest (line 485) | fn test_create_reference_from_tag_and_digest(

FILE: src/distribution/repository.rs
  type RepositoryList (line 16) | pub struct RepositoryList {
  function repository_list_success (line 27) | fn repository_list_success() -> Result<()> {
  function repository_list_failure (line 36) | fn repository_list_failure() {

FILE: src/distribution/tag.rs
  type TagList (line 16) | pub struct TagList {
  function tag_list_success (line 30) | fn tag_list_success() -> Result<()> {
  function tag_list_failure (line 41) | fn tag_list_failure() {

FILE: src/distribution/version.rs
  constant VERSION_MAJOR (line 4) | pub const VERSION_MAJOR: u32 = 1;
  constant VERSION_MINOR (line 7) | pub const VERSION_MINOR: u32 = 0;
  constant VERSION_PATCH (line 10) | pub const VERSION_PATCH: u32 = 0;
  constant VERSION_DEV (line 13) | pub const VERSION_DEV: &str = "-dev";
  constant VERSION (line 16) | pub const VERSION: &str = formatcp!("{VERSION_MAJOR}.{VERSION_MINOR}.{VE...
  function version (line 22) | pub fn version() -> String {
  function version_test (line 32) | fn version_test() {

FILE: src/error.rs
  type Result (line 11) | pub type Result<T> = std::result::Result<T, OciSpecError>;
  type OciSpecError (line 15) | pub enum OciSpecError {
  function oci_error (line 36) | pub(crate) fn oci_error<'a, M>(message: M) -> OciSpecError

FILE: src/image/annotations.rs
  constant ANNOTATION_CREATED (line 3) | pub const ANNOTATION_CREATED: &str = "org.opencontainers.image.created";
  constant ANNOTATION_AUTHORS (line 7) | pub const ANNOTATION_AUTHORS: &str = "org.opencontainers.image.authors";
  constant ANNOTATION_URL (line 11) | pub const ANNOTATION_URL: &str = "org.opencontainers.image.url";
  constant ANNOTATION_DOCUMENTATION (line 15) | pub const ANNOTATION_DOCUMENTATION: &str = "org.opencontainers.image.doc...
  constant ANNOTATION_SOURCE (line 19) | pub const ANNOTATION_SOURCE: &str = "org.opencontainers.image.source";
  constant ANNOTATION_VERSION (line 24) | pub const ANNOTATION_VERSION: &str = "org.opencontainers.image.version";
  constant ANNOTATION_REVISION (line 28) | pub const ANNOTATION_REVISION: &str = "org.opencontainers.image.revision";
  constant ANNOTATION_VENDOR (line 32) | pub const ANNOTATION_VENDOR: &str = "org.opencontainers.image.vendor";
  constant ANNOTATION_LICENSES (line 36) | pub const ANNOTATION_LICENSES: &str = "org.opencontainers.image.licenses";
  constant ANNOTATION_REF_NAME (line 41) | pub const ANNOTATION_REF_NAME: &str = "org.opencontainers.image.ref.name";
  constant ANNOTATION_TITLE (line 45) | pub const ANNOTATION_TITLE: &str = "org.opencontainers.image.title";
  constant ANNOTATION_DESCRIPTION (line 49) | pub const ANNOTATION_DESCRIPTION: &str = "org.opencontainers.image.descr...
  constant ANNOTATION_BASE_IMAGE_DIGEST (line 53) | pub const ANNOTATION_BASE_IMAGE_DIGEST: &str = "org.opencontainers.image...
  constant ANNOTATION_BASE_IMAGE_NAME (line 57) | pub const ANNOTATION_BASE_IMAGE_NAME: &str = "org.opencontainers.image.b...

FILE: src/image/artifact.rs
  type ArtifactManifest (line 23) | pub struct ArtifactManifest {
    method from_file (line 80) | pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
    method from_reader (line 99) | pub fn from_reader<R: Read>(reader: R) -> Result<Self> {
    method to_file (line 118) | pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
    method to_file_pretty (line 137) | pub fn to_file_pretty<P: AsRef<Path>>(&self, path: P) -> Result<()> {
    method to_writer (line 156) | pub fn to_writer<W: Write>(&self, writer: &mut W) -> Result<()> {
    method to_writer_pretty (line 175) | pub fn to_writer_pretty<W: Write>(&self, writer: &mut W) -> Result<()> {
    method to_string (line 193) | pub fn to_string(&self) -> Result<String> {
    method to_string_pretty (line 211) | pub fn to_string_pretty(&self) -> Result<String> {
  function get_manifest_path (line 222) | fn get_manifest_path() -> PathBuf {
  function create_manifest (line 226) | fn create_manifest() -> ArtifactManifest {
  function load_manifest_from_file (line 268) | fn load_manifest_from_file() {

FILE: src/image/config.rs
  constant LABEL_VERSION (line 20) | pub const LABEL_VERSION: &str = "version";
  type ImageConfiguration (line 46) | pub struct ImageConfiguration {
    method from_file (line 115) | pub fn from_file<P: AsRef<Path>>(path: P) -> Result<ImageConfiguration> {
    method from_reader (line 131) | pub fn from_reader<R: Read>(reader: R) -> Result<ImageConfiguration> {
    method to_file (line 147) | pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
    method to_file_pretty (line 163) | pub fn to_file_pretty<P: AsRef<Path>>(&self, path: P) -> Result<()> {
    method to_writer (line 179) | pub fn to_writer<W: Write>(&self, writer: &mut W) -> Result<()> {
    method to_writer_pretty (line 195) | pub fn to_writer_pretty<W: Write>(&self, writer: &mut W) -> Result<()> {
    method to_string (line 210) | pub fn to_string(&self) -> Result<String> {
    method to_string_pretty (line 225) | pub fn to_string_pretty(&self) -> Result<String> {
    method labels_of_config (line 230) | pub fn labels_of_config(&self) -> Option<&HashMap<String, String>> {
    method version (line 236) | pub fn version(&self) -> Option<&str> {
    method get_config_annotation (line 249) | pub fn get_config_annotation(&self, key: &str) -> Option<&str> {
  method fmt (line 259) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  type Config (line 294) | pub struct Config {
  type GoMapSerde (line 374) | struct GoMapSerde {}
  function deserialize_as_vec (line 376) | fn deserialize_as_vec<'de, D>(deserializer: D) -> std::result::Result<Op...
  function serialize_as_map (line 394) | fn serialize_as_map<S>(
  type RootFs (line 430) | pub struct RootFs {
  method default (line 441) | fn default() -> Self {
  type History (line 469) | pub struct History {
  function create_base_config (line 502) | fn create_base_config() -> ConfigBuilder {
  function create_base_imgconfig (line 524) | fn create_base_imgconfig(conf: Config) -> ImageConfigurationBuilder {
  function create_config (line 554) | fn create_config() -> ImageConfiguration {
  function create_imgconfig_v1 (line 561) | fn create_imgconfig_v1() -> ImageConfiguration {
  function get_config_path (line 575) | fn get_config_path() -> PathBuf {
  function load_configuration_from_file (line 580) | fn load_configuration_from_file() {
  function test_helpers (line 593) | fn test_helpers() {
  function load_configuration_from_reader (line 603) | fn load_configuration_from_reader() {
  function save_config_to_file (line 619) | fn save_config_to_file() {
  function save_config_to_writer (line 638) | fn save_config_to_writer() {
  function save_config_to_string (line 653) | fn save_config_to_string() {
  function optional_history_field_absent (line 666) | fn optional_history_field_absent() {
  function serialize_without_history (line 682) | fn serialize_without_history() {
  function builder_without_history (line 700) | fn builder_without_history() {

FILE: src/image/descriptor.rs
  type Descriptor (line 22) | pub struct Descriptor {
    method new (line 136) | pub fn new(media_type: MediaType, size: u64, digest: impl Into<Digest>...
    method as_digest_sha256 (line 150) | pub fn as_digest_sha256(&self) -> Option<&str> {
  type Platform (line 91) | pub struct Platform {
  function test_deserialize (line 165) | fn test_deserialize() {
  function test_malformed_digest (line 196) | fn test_malformed_digest() {

FILE: src/image/digest.rs
  type DigestAlgorithm (line 13) | pub enum DigestAlgorithm {
    method as_ref (line 28) | fn as_ref(&self) -> &str {
    method digest_hexlen (line 46) | pub const fn digest_hexlen(&self) -> Option<u32> {
    method from (line 57) | fn from(value: &str) -> Self {
  method fmt (line 39) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  function char_is_lowercase_ascii_hex (line 67) | fn char_is_lowercase_ascii_hex(c: char) -> bool {
  function char_is_algorithm_component (line 72) | fn char_is_algorithm_component(c: char) -> bool {
  function char_is_encoded (line 77) | fn char_is_encoded(c: char) -> bool {
  type Digest (line 99) | pub struct Digest {
    method as_ref (line 111) | fn as_ref(&self) -> &str {
    method deserialize (line 123) | fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    method serialize (line 133) | fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    constant ALGORITHM_SEPARATOR (line 143) | const ALGORITHM_SEPARATOR: &'static [char] = &['+', '.', '_', '-'];
    method algorithm (line 145) | pub fn algorithm(&self) -> &DigestAlgorithm {
    method digest (line 153) | pub fn digest(&self) -> &str {
    type Error (line 167) | type Error = crate::OciSpecError;
    method try_from (line 169) | fn try_from(s: String) -> Result<Self, Self::Error> {
    type Error (line 225) | type Error = crate::OciSpecError;
    method try_from (line 227) | fn try_from(string: &str) -> Result<Self, Self::Error> {
    method from (line 239) | fn from(value: Sha256Digest) -> Self {
  method fmt (line 117) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  type Err (line 159) | type Err = crate::OciSpecError;
  method from_str (line 161) | fn from_str(s: &str) -> Result<Self, Self::Err> {
  type Sha256Digest (line 234) | pub struct Sha256Digest {
    method as_ref (line 249) | fn as_ref(&self) -> &str {
    method digest (line 280) | pub fn digest(&self) -> &str {
  method fmt (line 255) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  type Err (line 261) | type Err = crate::OciSpecError;
  method from_str (line 263) | fn from_str(digest: &str) -> Result<Self, Self::Err> {
  function test_digest_invalid (line 290) | fn test_digest_invalid() {
  constant VALID_DIGEST_SHA256 (line 316) | const VALID_DIGEST_SHA256: &str =
  constant VALID_DIGEST_SHA384 (line 318) | const VALID_DIGEST_SHA384: &str =
  constant VALID_DIGEST_SHA512 (line 320) | const VALID_DIGEST_SHA512: &str =
  function test_digest_valid (line 324) | fn test_digest_valid() {
  function test_sha256_valid (line 337) | fn test_sha256_valid() {
  function test_sha384_valid (line 347) | fn test_sha384_valid() {
  function test_sha512_valid (line 359) | fn test_sha512_valid() {
  function test_sha256 (line 369) | fn test_sha256() {

FILE: src/image/index.rs
  constant SCHEMA_VERSION (line 17) | pub const SCHEMA_VERSION: u32 = 2;
  type ImageIndex (line 32) | pub struct ImageIndex {
    method from_file (line 86) | pub fn from_file<P: AsRef<Path>>(path: P) -> Result<ImageIndex> {
    method from_reader (line 102) | pub fn from_reader<R: Read>(reader: R) -> Result<ImageIndex> {
    method to_file (line 118) | pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
    method to_file_pretty (line 134) | pub fn to_file_pretty<P: AsRef<Path>>(&self, path: P) -> Result<()> {
    method to_writer (line 150) | pub fn to_writer<W: Write>(&self, writer: &mut W) -> Result<()> {
    method to_writer_pretty (line 166) | pub fn to_writer_pretty<W: Write>(&self, writer: &mut W) -> Result<()> {
    method to_string (line 181) | pub fn to_string(&self) -> Result<String> {
    method to_string_pretty (line 196) | pub fn to_string_pretty(&self) -> Result<String> {
  method default (line 202) | fn default() -> Self {
  method fmt (line 218) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  function create_index (line 239) | fn create_index() -> ImageIndex {
  function get_index_path (line 285) | fn get_index_path() -> PathBuf {
  function load_index_from_file (line 290) | fn load_index_from_file() {
  function load_index_from_reader (line 303) | fn load_index_from_reader() {
  function save_index_to_file (line 316) | fn save_index_to_file() {
  function save_index_to_writer (line 335) | fn save_index_to_writer() {
  function save_index_to_string (line 349) | fn save_index_to_string() {

FILE: src/image/manifest.rs
  type ImageManifest (line 39) | pub struct ImageManifest {
    method from_file (line 111) | pub fn from_file<P: AsRef<Path>>(path: P) -> Result<ImageManifest> {
    method from_reader (line 127) | pub fn from_reader<R: Read>(reader: R) -> Result<ImageManifest> {
    method to_file (line 143) | pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
    method to_file_pretty (line 159) | pub fn to_file_pretty<P: AsRef<Path>>(&self, path: P) -> Result<()> {
    method to_writer (line 175) | pub fn to_writer<W: Write>(&self, writer: &mut W) -> Result<()> {
    method to_writer_pretty (line 191) | pub fn to_writer_pretty<W: Write>(&self, writer: &mut W) -> Result<()> {
    method to_string (line 206) | pub fn to_string(&self) -> Result<String> {
    method to_string_pretty (line 221) | pub fn to_string_pretty(&self) -> Result<String> {
  method fmt (line 230) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  function create_manifest (line 249) | fn create_manifest() -> ImageManifest {
  function get_manifest_path (line 296) | fn get_manifest_path() -> PathBuf {
  function load_manifest_from_file (line 301) | fn load_manifest_from_file() {
  function getset (line 314) | fn getset() {
  function load_manifest_from_reader (line 323) | fn load_manifest_from_reader() {
  function save_manifest_to_file (line 336) | fn save_manifest_to_file() {
  function save_manifest_to_writer (line 355) | fn save_manifest_to_writer() {
  function save_manifest_to_string (line 369) | fn save_manifest_to_string() {

FILE: src/image/mod.rs
  type MediaType (line 30) | pub enum MediaType {
    method from (line 81) | fn from(media_type: &str) -> Self {
    method as_ref (line 114) | fn as_ref(&self) -> &str {
    method deserialize (line 176) | fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
  method fmt (line 75) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  method from (line 108) | fn from(media_type: MediaType) -> Self {
  type ToDockerV2S2 (line 147) | pub trait ToDockerV2S2 {
    method to_docker_v2s2 (line 150) | fn to_docker_v2s2(&self) -> Result<&str, std::fmt::Error>;
    method to_docker_v2s2 (line 154) | fn to_docker_v2s2(&self) -> Result<&str, std::fmt::Error> {
  method serialize (line 166) | fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
  type Os (line 188) | pub enum Os {
    method from (line 212) | fn from(os: &str) -> Self {
    method deserialize (line 274) | fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
  method fmt (line 237) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  method serialize (line 264) | fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
  method default (line 284) | fn default() -> Self {
  type Arch (line 291) | pub enum Arch {
    method from (line 382) | fn from(arch: &str) -> Self {
    method deserialize (line 424) | fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
  method fmt (line 348) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  method serialize (line 414) | fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
  method default (line 434) | fn default() -> Self {
  function test_arch_translation (line 455) | fn test_arch_translation() {
  function test_asref (line 464) | fn test_asref() {

FILE: src/image/oci_layout.rs
  type OciLayout (line 24) | pub struct OciLayout {
    method from_file (line 43) | pub fn from_file<P: AsRef<Path>>(path: P) -> Result<OciLayout> {
    method from_reader (line 59) | pub fn from_reader<R: Read>(reader: R) -> Result<OciLayout> {
    method to_file (line 75) | pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
    method to_file_pretty (line 91) | pub fn to_file_pretty<P: AsRef<Path>>(&self, path: P) -> Result<()> {
    method to_writer (line 107) | pub fn to_writer<W: Write>(&self, writer: &mut W) -> Result<()> {
    method to_writer_pretty (line 123) | pub fn to_writer_pretty<W: Write>(&self, writer: &mut W) -> Result<()> {
    method to_string (line 138) | pub fn to_string(&self) -> Result<String> {
    method to_string_pretty (line 153) | pub fn to_string_pretty(&self) -> Result<String> {
  function create_oci_layout (line 164) | fn create_oci_layout() -> OciLayout {
  function get_oci_layout_path (line 171) | fn get_oci_layout_path() -> PathBuf {
  function load_oci_layout_from_file (line 176) | fn load_oci_layout_from_file() {
  function load_oci_layout_from_reader (line 189) | fn load_oci_layout_from_reader() {
  function save_oci_layout_to_file (line 202) | fn save_oci_layout_to_file() {
  function save_oci_layout_to_writer (line 221) | fn save_oci_layout_to_writer() {
  function save_oci_layout_to_string (line 235) | fn save_oci_layout_to_string() {

FILE: src/image/version.rs
  constant VERSION_MAJOR (line 4) | pub const VERSION_MAJOR: u32 = 1;
  constant VERSION_MINOR (line 7) | pub const VERSION_MINOR: u32 = 0;
  constant VERSION_PATCH (line 10) | pub const VERSION_PATCH: u32 = 1;
  constant VERSION_DEV (line 13) | pub const VERSION_DEV: &str = "-dev";
  constant VERSION (line 16) | pub const VERSION: &str = formatcp!("{VERSION_MAJOR}.{VERSION_MINOR}.{VE...
  function version (line 22) | pub fn version() -> String {
  function version_test (line 32) | fn version_test() {

FILE: src/lib.rs
  function from_file (line 23) | fn from_file<P: AsRef<Path>, T: DeserializeOwned>(path: P) -> Result<T> {
  function from_reader (line 30) | fn from_reader<R: Read, T: DeserializeOwned>(reader: R) -> Result<T> {
  function to_file (line 35) | fn to_file<P: AsRef<Path>, T: Serialize>(item: &T, path: P, pretty: bool...
  function to_writer (line 52) | fn to_writer<W: Write, T: Serialize>(item: &T, writer: &mut W, pretty: b...
  function to_string (line 61) | fn to_string<T: Serialize>(item: &T, pretty: bool) -> Result<String> {
  function is_none_or_empty (line 69) | fn is_none_or_empty<C>(opt: &Option<C>) -> bool

FILE: src/runtime/capability.rs
  type Capabilities (line 10) | pub type Capabilities = HashSet<Capability>;
  type Capability (line 27) | pub enum Capability {
    method deserialize (line 523) | fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
  function serialize (line 597) | fn serialize() {
  function deserialize (line 604) | fn deserialize() -> Result<()> {
  function capabilities (line 613) | fn capabilities() -> Result<()> {
  function invalid_string2enum (line 629) | fn invalid_string2enum() {
  function cap_enum_to_string (line 636) | fn cap_enum_to_string() {
  function cap_string_to_enum (line 648) | fn cap_string_to_enum() {
  function test_serde_serialization (line 663) | fn test_serde_serialization() {
  function test_serde_deserialization (line 674) | fn test_serde_deserialization() {
  function deserialize_one_more_cap_prefix (line 685) | fn deserialize_one_more_cap_prefix() -> Result<()> {

FILE: src/runtime/features.rs
  type Features (line 36) | pub struct Features {
  type LinuxFeature (line 81) | pub struct LinuxFeature {
  type Cgroup (line 128) | pub struct Cgroup {
  type Seccomp (line 173) | pub struct Seccomp {
  type Apparmor (line 218) | pub struct Apparmor {
  type Selinux (line 247) | pub struct Selinux {
  type IntelRdt (line 276) | pub struct IntelRdt {
  type MemoryPolicy (line 316) | pub struct MemoryPolicy {
  type MountExtensions (line 345) | pub struct MountExtensions {
  type NetDevices (line 372) | pub struct NetDevices {
  type IDMap (line 400) | pub struct IDMap {
  function test_parse_features (line 414) | fn test_parse_features() {

FILE: src/runtime/hooks.rs
  type Hooks (line 30) | pub struct Hooks {
  type Hook (line 101) | pub struct Hook {

FILE: src/runtime/linux.rs
  type Linux (line 23) | pub struct Linux {
    method rootless (line 150) | pub fn rootless(uid: u32, gid: u32) -> Self {
  method default (line 107) | fn default() -> Self {
  type LinuxIdMapping (line 187) | pub struct LinuxIdMapping {
  type LinuxDeviceType (line 206) | pub enum LinuxDeviceType {
    method as_str (line 232) | pub fn as_str(&self) -> &str {
  method default (line 225) | fn default() -> LinuxDeviceType {
  type LinuxNetDevice (line 263) | pub struct LinuxNetDevice {
  type LinuxDeviceCgroup (line 292) | pub struct LinuxDeviceCgroup {
    method from (line 1036) | fn from(linux_device: &LinuxDevice) -> LinuxDeviceCgroup {
  method fmt (line 323) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  type LinuxMemory (line 366) | pub struct LinuxMemory {
  type LinuxCpu (line 443) | pub struct LinuxCpu {
  type LinuxPids (line 514) | pub struct LinuxPids {
  type LinuxWeightDevice (line 533) | pub struct LinuxWeightDevice {
  type LinuxThrottleDevice (line 563) | pub struct LinuxThrottleDevice {
  type LinuxBlockIo (line 598) | pub struct LinuxBlockIo {
  type LinuxHugepageLimit (line 664) | pub struct LinuxHugepageLimit {
  type LinuxInterfacePriority (line 697) | pub struct LinuxInterfacePriority {
  method fmt (line 713) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  type LinuxNetwork (line 740) | pub struct LinuxNetwork {
  type LinuxResources (line 774) | pub struct LinuxResources {
  type LinuxRdma (line 845) | pub struct LinuxRdma {
  type LinuxNamespaceType (line 863) | pub enum LinuxNamespaceType {
    type Error (line 893) | type Error = OciSpecError;
    method try_from (line 895) | fn try_from(namespace: &str) -> Result<Self, Self::Error> {
  type LinuxNamespace (line 932) | pub struct LinuxNamespace {
  function get_default_namespaces (line 946) | pub fn get_default_namespaces() -> Vec<LinuxNamespace> {
  type LinuxDevice (line 998) | pub struct LinuxDevice {
  type LinuxSeccomp (line 1068) | pub struct LinuxSeccomp {
  type LinuxSeccompAction (line 1110) | pub enum LinuxSeccompAction {
    method as_u32 (line 1153) | pub fn as_u32(&self, errno_ret: Option<u32>) -> u32 {
  function from (line 1142) | fn from(action: LinuxSeccompAction) -> Self {
  type Arch (line 1185) | pub enum Arch {
  type LinuxSeccompFilterFlag (line 1266) | pub enum LinuxSeccompFilterFlag {
  type LinuxSeccompOperator (line 1297) | pub enum LinuxSeccompOperator {
  type LinuxSyscall (line 1342) | pub struct LinuxSyscall {
  type LinuxSeccompArg (line 1374) | pub struct LinuxSeccompArg {
  function get_default_maskedpaths (line 1390) | pub fn get_default_maskedpaths() -> Vec<String> {
  function get_default_readonly_paths (line 1409) | pub fn get_default_readonly_paths() -> Vec<String> {
  type LinuxIntelRdt (line 1443) | pub struct LinuxIntelRdt {
  type LinuxPersonality (line 1522) | pub struct LinuxPersonality {
  type LinuxPersonalityDomain (line 1537) | pub enum LinuxPersonalityDomain {
  type LinuxMemoryPolicy (line 1570) | pub struct LinuxMemoryPolicy {
  method default (line 1588) | fn default() -> Self {
  type MemoryPolicyModeType (line 1601) | pub enum MemoryPolicyModeType {
  type MemoryPolicyFlagType (line 1641) | pub enum MemoryPolicyFlagType {
  type LinuxTimeOffset (line 1678) | pub struct LinuxTimeOffset {
  function some_none_generator_util (line 1693) | fn some_none_generator_util<T: Arbitrary>(g: &mut Gen) -> Option<T> {
  method arbitrary (line 1703) | fn arbitrary(g: &mut Gen) -> LinuxDeviceCgroup {
  method arbitrary (line 1731) | fn arbitrary(g: &mut Gen) -> LinuxMemory {
  method arbitrary (line 1748) | fn arbitrary(g: &mut Gen) -> LinuxHugepageLimit {
  function device_type_enum_to_str (line 1766) | fn device_type_enum_to_str() {
  function device_type_string_to_enum (line 1778) | fn device_type_string_to_enum() {
  function ns_type_enum_to_string (line 1798) | fn ns_type_enum_to_string() {
  function ns_type_string_to_enum (line 1810) | fn ns_type_string_to_enum() {
  function seccomp_action_enum_to_string (line 1838) | fn seccomp_action_enum_to_string() {
  function seccomp_action_string_to_enum (line 1850) | fn seccomp_action_string_to_enum() {
  function seccomp_arch_enum_to_string (line 1870) | fn seccomp_arch_enum_to_string() {
  function seccomp_arch_string_to_enum (line 1900) | fn seccomp_arch_string_to_enum() {
  function seccomp_filter_flag_enum_to_string (line 1944) | fn seccomp_filter_flag_enum_to_string() {
  function seccomp_filter_flag_string_to_enum (line 1959) | fn seccomp_filter_flag_string_to_enum() {
  function seccomp_operator_enum_to_string (line 1995) | fn seccomp_operator_enum_to_string() {
  function seccomp_action_as_u32 (line 2008) | fn seccomp_action_as_u32() {
  function seccomp_operator_string_to_enum (line 2023) | fn seccomp_operator_string_to_enum() {
  function memory_policy_mode_enum_to_string (line 2043) | fn memory_policy_mode_enum_to_string() {
  function memory_policy_mode_string_to_enum (line 2067) | fn memory_policy_mode_string_to_enum() {
  function memory_policy_flag_enum_to_string (line 2103) | fn memory_policy_flag_enum_to_string() {
  function memory_policy_flag_string_to_enum (line 2115) | fn memory_policy_flag_string_to_enum() {
  function test_linux_memory_policy_serialization (line 2134) | fn test_linux_memory_policy_serialization() {
  function test_linux_memory_policy_default (line 2159) | fn test_linux_memory_policy_default() {

FILE: src/runtime/miscellaneous.rs
  type Root (line 19) | pub struct Root {
  method default (line 35) | fn default() -> Self {
  type Mount (line 64) | pub struct Mount {
  function get_default_mounts (line 115) | pub fn get_default_mounts() -> Vec<Mount> {
  method validate (line 211) | fn validate(&self) -> Result<(), OciSpecError> {
  function get_rootless_mounts (line 241) | pub fn get_rootless_mounts() -> Vec<Mount> {

FILE: src/runtime/mod.rs
  type Spec (line 57) | pub struct Spec {
    method load (line 226) | pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
    method save (line 245) | pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
    method canonicalize_rootfs (line 255) | pub fn canonicalize_rootfs<P: AsRef<Path>>(&mut self, bundle: P) -> Re...
    method rootless (line 278) | pub fn rootless(uid: u32, gid: u32) -> Self {
    method canonicalize_path (line 286) | fn canonicalize_path<B, P>(bundle: B, path: P) -> Result<PathBuf>
  method default (line 192) | fn default() -> Self {
  function test_canonicalize_rootfs (line 305) | fn test_canonicalize_rootfs() {
  function test_load_save (line 361) | fn test_load_save() {
  function test_rootless (line 379) | fn test_rootless() {

FILE: src/runtime/process.rs
  type Process (line 35) | pub struct Process {
  method default (line 126) | fn default() -> Self {
  type Box (line 183) | pub struct Box {
  type PosixRlimitType (line 199) | pub enum PosixRlimitType {
  type PosixRlimit (line 267) | pub struct PosixRlimit {
  type User (line 303) | pub struct User {
  type LinuxCapabilities (line 341) | pub struct LinuxCapabilities {
  method default (line 368) | fn default() -> Self {
  type LinuxIOPriority (line 396) | pub struct LinuxIOPriority {
  type IOPriorityClass (line 412) | pub enum IOPriorityClass {
  type Scheduler (line 446) | pub struct Scheduler {
  method default (line 479) | fn default() -> Self {
  type LinuxSchedulerPolicy (line 496) | pub enum LinuxSchedulerPolicy {
  method default (line 515) | fn default() -> Self {
  type LinuxSchedulerFlag (line 524) | pub enum LinuxSchedulerFlag {
  method default (line 543) | fn default() -> Self {
  type ExecCPUAffinity (line 560) | pub struct ExecCPUAffinity {
  method validate (line 586) | fn validate(&self) -> Result<(), OciSpecError> {
  function deserialize (line 599) | fn deserialize<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
  function exec_cpu_affinity_regex (line 612) | fn exec_cpu_affinity_regex() -> &'static Regex {
  function validate_cpu_affinity (line 620) | fn validate_cpu_affinity(s: &str) -> Result<(), String> {
  function posix_rlimit_type_enum_to_string (line 635) | fn posix_rlimit_type_enum_to_string() {
  function posix_rlimit_type_string_to_enum (line 647) | fn posix_rlimit_type_string_to_enum() {
  function exec_cpu_affinity_valid_initial_final (line 666) | fn exec_cpu_affinity_valid_initial_final() {
  function exec_cpu_affinity_invalid_initial (line 681) | fn exec_cpu_affinity_invalid_initial() {
  function exec_cpu_affinity_invalid_final (line 688) | fn exec_cpu_affinity_invalid_final() {
  function exec_cpu_affinity_valid_final (line 695) | fn exec_cpu_affinity_valid_final() {
  function exec_cpu_affinity_valid_initial (line 703) | fn exec_cpu_affinity_valid_initial() {
  function exec_cpu_affinity_empty (line 711) | fn exec_cpu_affinity_empty() {
  function test_build_valid_input (line 721) | fn test_build_valid_input() {
  function test_build_invalid_initial (line 733) | fn test_build_invalid_initial() {
  function test_build_invalid_final (line 750) | fn test_build_invalid_final() {
  function test_build_empty (line 767) | fn test_build_empty() {

FILE: src/runtime/solaris.rs
  type Solaris (line 19) | pub struct Solaris {
  type SolarisAnet (line 61) | pub struct SolarisAnet {
  type SolarisCappedCPU (line 104) | pub struct SolarisCappedCPU {
  type SolarisCappedMemory (line 122) | pub struct SolarisCappedMemory {

FILE: src/runtime/state.rs
  type ContainerState (line 17) | pub enum ContainerState {
  method fmt (line 36) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  type State (line 68) | pub struct State {
    method load (line 99) | pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, OciSpecError> {
    method save (line 111) | pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), OciSpecError> {
  constant SECCOMP_FD_NAME (line 124) | pub const SECCOMP_FD_NAME: &str = "seccompFd";
  type ContainerProcessState (line 148) | pub struct ContainerProcessState {
  function test_load_save (line 177) | fn test_load_save() {

FILE: src/runtime/test.rs
  function serialize_and_deserialize_spec (line 5) | fn serialize_and_deserialize_spec() {
  function test_linux_device_cgroup_to_string (line 13) | fn test_linux_device_cgroup_to_string() {
  function test_load_sample_spec (line 34) | fn test_load_sample_spec() {
  function test_load_sample_state (line 42) | fn test_load_sample_state() {
  function test_load_sample_windows_spec (line 50) | fn test_load_sample_windows_spec() {
  function test_load_sample_zos_spec (line 58) | fn test_load_sample_zos_spec() {
  function test_linux_netdevice_lifecycle (line 66) | fn test_linux_netdevice_lifecycle() {

FILE: src/runtime/version.rs
  constant VERSION_MAJOR (line 4) | pub const VERSION_MAJOR: u32 = 1;
  constant VERSION_MINOR (line 7) | pub const VERSION_MINOR: u32 = 1;
  constant VERSION_PATCH (line 10) | pub const VERSION_PATCH: u32 = 0;
  constant VERSION_DEV (line 13) | pub const VERSION_DEV: &str = "-dev";
  constant VERSION (line 16) | pub const VERSION: &str = formatcp!("{VERSION_MAJOR}.{VERSION_MINOR}.{VE...
  function version (line 22) | pub fn version() -> String {
  function version_test (line 32) | fn version_test() {

FILE: src/runtime/vm.rs
  type VM (line 18) | pub struct VM {
  type VMHypervisor (line 46) | pub struct VMHypervisor {
  type VMKernel (line 68) | pub struct VMKernel {
  type VMImage (line 94) | pub struct VMImage {

FILE: src/runtime/windows.rs
  type Windows (line 29) | pub struct Windows {
  type WindowsDevice (line 91) | pub struct WindowsDevice {
  type WindowsResources (line 110) | pub struct WindowsResources {
  type WindowsMemoryResources (line 135) | pub struct WindowsMemoryResources {
  type WindowsCPUResources (line 152) | pub struct WindowsCPUResources {
  type WindowsStorageResources (line 179) | pub struct WindowsStorageResources {
  type WindowsHyperV (line 207) | pub struct WindowsHyperV {
  type WindowsNetwork (line 225) | pub struct WindowsNetwork {

FILE: src/runtime/zos.rs
  type ZOS (line 29) | pub struct ZOS {
  type ZOSNamespaceType (line 41) | pub enum ZOSNamespaceType {
    type Error (line 54) | type Error = OciSpecError;
    method try_from (line 56) | fn try_from(namespace: &str) -> Result<Self, Self::Error> {
  type ZOSNamespace (line 89) | pub struct ZOSNamespace {
Condensed preview — 67 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (443K chars).
[
  {
    "path": ".codecov.yml",
    "chars": 211,
    "preview": "---\ncodecov:\n  notify:\n    after_n_builds: 1\n    require_ci_to_pass: false\n\ncoverage:\n  precision: 2\n  round: down\n  ran"
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "chars": 352,
    "preview": "{\n\t\"name\": \"oci-spec-rs\",\n\t\"image\": \"mcr.microsoft.com/devcontainers/rust:1-bullseye\",\n\t\"customizations\": {\n\t\t\"vscode\": "
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "chars": 2402,
    "preview": "<!--  Thanks for sending a pull request!\n\nPlease be aware that we're following the Kubernetes guidelines of contributing"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 503,
    "preview": "version: 2\nupdates:\n  - package-ecosystem: cargo\n    directory: \"/\"\n    schedule:\n      interval: daily\n      time: \"11:"
  },
  {
    "path": ".github/grcov.yml",
    "chars": 139,
    "preview": "branch: true\nignore-not-existing: true\nllvm: true\nfilter: covered\noutput-type: lcov\noutput-path: ./lcov.info\nprefix-dir:"
  },
  {
    "path": ".github/release.yml",
    "chars": 525,
    "preview": "changelog:\n  exclude:\n    authors:\n      - dependabot\n  categories:\n    - title: Breaking Changes\n      labels:\n        "
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 2726,
    "preview": "name: ci\non:\n  pull_request: {}\n  push:\n    branches:\n      - main\nenv:\n  CARGO_TERM_COLOR: always\njobs:\n  build:\n    ru"
  },
  {
    "path": ".github/workflows/cross.yml",
    "chars": 1327,
    "preview": "name: cross\non:\n  pull_request: {}\n  push:\n    branches:\n      - main\nenv:\n  CARGO_TERM_COLOR: always\njobs:\n  build:\n   "
  },
  {
    "path": ".github/workflows/gh-pages.yml",
    "chars": 963,
    "preview": "name: gh-pages\non:\n  push:\n    branches:\n      - main\nenv:\n  CARGO_TERM_COLOR: always\njobs:\n  update:\n    runs-on: ubunt"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 646,
    "preview": "name: Release\n\non:\n  push:\n    tags: [ \"v[0-9]+.[0-9]+.[0-9]+*\" ]\n\njobs:\n  publish:\n    name: Publish Packages\n    runs-"
  },
  {
    "path": ".github/workflows/security-audit.yml",
    "chars": 742,
    "preview": "name: Security audit\non:\n  schedule:\n    - cron: \"0 0 * * *\"\n  push:\n    paths:\n      - \"**/Cargo.toml\"\n      - \"**/Carg"
  },
  {
    "path": ".gitignore",
    "chars": 320,
    "preview": "# Generated by Cargo\n# will have compiled files and executables\n/target/\n\n# Remove Cargo.lock from gitignore if creating"
  },
  {
    "path": ".rustfmt.toml",
    "chars": 522,
    "preview": "max_width = 100\nhard_tabs = false\ntab_spaces = 4\nnewline_style = \"Auto\"\nuse_small_heuristics = \"Default\"\nfn_call_width ="
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 305,
    "preview": "# Code of Conduct\n\nWe follow the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md)"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 4858,
    "preview": "# Contributing to oci-spec-rs\n\nWe'd love to have you join the community! Below summarizes the processes\nthat we follow.\n"
  },
  {
    "path": "Cargo.toml",
    "chars": 1060,
    "preview": "[package]\nname = \"oci-spec\"\nversion = \"0.9.0\"\nedition = \"2021\"\nauthors = [\n    \"Furisto\",\n    \"Sascha Grunert <sgrunert@"
  },
  {
    "path": "GOVERNANCE.md",
    "chars": 6524,
    "preview": "# oci-spec-rs Project Governance\n\nThe oci-spec-rs project is dedicated to creating an OCI Runtime, Image and\nDistributio"
  },
  {
    "path": "LICENSE",
    "chars": 11344,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "MAINTAINERS.md",
    "chars": 1256,
    "preview": "The current Maintainers Group for the oci-spec-rs project consists of:\n\n| Name            | Employer           | GitHub "
  },
  {
    "path": "OWNERS",
    "chars": 125,
    "preview": "filters:\n  .*:\n    reviewers:\n    - oci-spec-rs-reviewers\n    approvers:\n    - oci-spec-rs-approvers\n    - youki-maintai"
  },
  {
    "path": "OWNERS_ALIASES",
    "chars": 222,
    "preview": "aliases:\n  oci-spec-rs-approvers:\n    - cgwalters\n    - flavio\n    - saschagrunert\n    - thomastaylor312\n    - utam0k\n  "
  },
  {
    "path": "README.md",
    "chars": 5105,
    "preview": "# oci-spec-rs\n\n[![ci](https://github.com/containers/oci-spec-rs/workflows/ci/badge.svg)](https://github.com/containers/o"
  },
  {
    "path": "SECURITY.md",
    "chars": 1592,
    "preview": "# oci-spec-rs Security\n\nSecurity is taken seriously and has high priority across all related projects to\nensure users ca"
  },
  {
    "path": "SECURITY_CONTACTS",
    "chars": 549,
    "preview": "# Defined below are the security contacts for this repo.\n#\n# They are the contact point for the Product Security Team to"
  },
  {
    "path": "hack/release",
    "chars": 939,
    "preview": "#!/usr/bin/env bash\nset -euo pipefail\n\nVERSION=${1:-}\n\nif [[ -z \"$VERSION\" ]]; then\n    echo \"Please provide a version t"
  },
  {
    "path": "release.md",
    "chars": 556,
    "preview": "# Releasing a new version of oci-spec-rs\n\nA new release of this crate can be proposed by running the version bump script"
  },
  {
    "path": "src/distribution/error.rs",
    "chars": 6208,
    "preview": "//! Error types of the distribution spec.\n\nuse crate::error::OciSpecError;\nuse derive_builder::Builder;\nuse getset::Gett"
  },
  {
    "path": "src/distribution/mod.rs",
    "chars": 959,
    "preview": "//! [OCI distribution spec](https://github.com/opencontainers/distribution-spec) types and definitions.\n//!\n//! The Open"
  },
  {
    "path": "src/distribution/reference.rs",
    "chars": 20770,
    "preview": "use std::fmt;\nuse std::str::FromStr;\nuse std::{convert::TryFrom, sync::OnceLock};\n\nuse regex::{Regex, RegexBuilder};\nuse"
  },
  {
    "path": "src/distribution/repository.rs",
    "chars": 1033,
    "preview": "//! Repository types of the distribution spec.\n\nuse crate::error::OciSpecError;\nuse derive_builder::Builder;\nuse getset:"
  },
  {
    "path": "src/distribution/tag.rs",
    "chars": 1052,
    "preview": "//! Tag types of the distribution spec.\n\nuse crate::error::OciSpecError;\nuse derive_builder::Builder;\nuse getset::{Gette"
  },
  {
    "path": "src/distribution/version.rs",
    "chars": 842,
    "preview": "use const_format::formatcp;\n\n/// API incompatible changes.\npub const VERSION_MAJOR: u32 = 1;\n\n/// Changing functionality"
  },
  {
    "path": "src/error.rs",
    "chars": 1355,
    "preview": "//! Error types of the crate.\n\nuse std::{borrow::Cow, io};\nuse thiserror::Error;\n\n/// Spezialized result type for oci sp"
  },
  {
    "path": "src/image/annotations.rs",
    "chars": 2852,
    "preview": "/// AnnotationCreated is the annotation key for the date and time on which the\n/// image was built (date-time string as "
  },
  {
    "path": "src/image/artifact.rs",
    "chars": 9359,
    "preview": "use super::{Descriptor, MediaType};\nuse crate::error::{OciSpecError, Result};\nuse derive_builder::Builder;\nuse getset::{"
  },
  {
    "path": "src/image/config.rs",
    "chars": 25725,
    "preview": "use super::{Arch, Os};\nuse crate::{\n    error::{OciSpecError, Result},\n    from_file, from_reader, to_file, to_string, t"
  },
  {
    "path": "src/image/descriptor.rs",
    "chars": 8807,
    "preview": "use super::{Arch, Digest, MediaType, Os};\nuse crate::error::OciSpecError;\nuse derive_builder::Builder;\nuse getset::{Copy"
  },
  {
    "path": "src/image/digest.rs",
    "chars": 12046,
    "preview": "//! Functionality corresponding to <https://github.com/opencontainers/image-spec/blob/main/descriptor.md#digests>.\n\nuse "
  },
  {
    "path": "src/image/index.rs",
    "chars": 12624,
    "preview": "use super::{Descriptor, MediaType};\nuse crate::{\n    error::{OciSpecError, Result},\n    from_file, from_reader, to_file,"
  },
  {
    "path": "src/image/manifest.rs",
    "chars": 13694,
    "preview": "use super::{Descriptor, MediaType};\nuse crate::{\n    error::{OciSpecError, Result},\n    from_file, from_reader, to_file,"
  },
  {
    "path": "src/image/mod.rs",
    "chars": 15740,
    "preview": "//! [OCI image spec](https://github.com/opencontainers/image-spec) types and definitions.\n\nmod annotations;\nmod artifact"
  },
  {
    "path": "src/image/oci_layout.rs",
    "chars": 8163,
    "preview": "use crate::{\n    error::{OciSpecError, Result},\n    from_file, from_reader, to_file, to_string, to_writer,\n};\nuse derive"
  },
  {
    "path": "src/image/version.rs",
    "chars": 842,
    "preview": "use const_format::formatcp;\n\n/// API incompatible changes.\npub const VERSION_MAJOR: u32 = 1;\n\n/// Changing functionality"
  },
  {
    "path": "src/lib.rs",
    "chars": 1995,
    "preview": "#![deny(missing_docs, warnings)]\n#![doc = include_str!(\"../README.md\")]\n#![allow(clippy::too_long_first_doc_paragraph)]\n"
  },
  {
    "path": "src/runtime/capability.rs",
    "chars": 30739,
    "preview": "use serde::{\n    de::{Deserializer, Error},\n    Deserialize, Serialize,\n};\nuse std::collections::HashSet;\n\nuse strum_mac"
  },
  {
    "path": "src/runtime/features.rs",
    "chars": 26503,
    "preview": "use std::collections::HashMap;\n\nuse crate::{\n    error::OciSpecError,\n    runtime::{Arch, LinuxNamespaceType, LinuxSecco"
  },
  {
    "path": "src/runtime/hooks.rs",
    "chars": 4696,
    "preview": "use crate::error::OciSpecError;\nuse derive_builder::Builder;\nuse getset::{CopyGetters, Getters, MutGetters, Setters};\nus"
  },
  {
    "path": "src/runtime/linux.rs",
    "chars": 68820,
    "preview": "use crate::error::{oci_error, OciSpecError};\nuse crate::is_none_or_empty;\n\nuse derive_builder::Builder;\nuse getset::{Cop"
  },
  {
    "path": "src/runtime/miscellaneous.rs",
    "chars": 8249,
    "preview": "use crate::error::OciSpecError;\nuse crate::runtime::LinuxIdMapping;\nuse derive_builder::Builder;\nuse getset::{CopyGetter"
  },
  {
    "path": "src/runtime/mod.rs",
    "chars": 17685,
    "preview": "//! [OCI runtime spec](https://github.com/opencontainers/runtime-spec) types and definitions.\n//!\n//! [`Spec`] represent"
  },
  {
    "path": "src/runtime/process.rs",
    "chars": 26982,
    "preview": "use crate::{\n    error::OciSpecError,\n    runtime::{Capabilities, Capability},\n};\nuse derive_builder::Builder;\nuse getse"
  },
  {
    "path": "src/runtime/solaris.rs",
    "chars": 4421,
    "preview": "use crate::error::OciSpecError;\nuse derive_builder::Builder;\nuse getset::{Getters, Setters};\nuse serde::{Deserialize, Se"
  },
  {
    "path": "src/runtime/state.rs",
    "chars": 5802,
    "preview": "use crate::error::OciSpecError;\n\nuse std::{\n    fs,\n    io::{BufReader, BufWriter, Write},\n    path::{Path, PathBuf},\n};"
  },
  {
    "path": "src/runtime/test/fixture/sample.json",
    "chars": 10680,
    "preview": "{\n    \"ociVersion\": \"0.5.0-dev\",\n    \"process\": {\n        \"terminal\": true,\n        \"user\": {\n            \"uid\": 1,\n    "
  },
  {
    "path": "src/runtime/test/fixture/sample_state.json",
    "chars": 190,
    "preview": "{\n    \"ociVersion\": \"1.0.2\",\n    \"id\": \"oci-container1\",\n    \"status\": \"running\",\n    \"pid\": 4422,\n    \"bundle\": \"/conta"
  },
  {
    "path": "src/runtime/test/fixture/sample_windows.json",
    "chars": 845,
    "preview": "{\n    \"ociVersion\": \"0.5.0-dev\",\n    \"process\": {\n        \"terminal\": true,\n        \"user\": {\n            \"uid\": 1,\n    "
  },
  {
    "path": "src/runtime/test/fixture/sample_zos.json",
    "chars": 3074,
    "preview": "{\n    \"ociVersion\": \"1.2.1\",\n    \"process\": {\n        \"terminal\": true,\n        \"user\": {\n            \"uid\": 1,\n        "
  },
  {
    "path": "src/runtime/test.rs",
    "chars": 3344,
    "preview": "#[cfg(test)]\nuse super::*;\n\n#[test]\nfn serialize_and_deserialize_spec() {\n    let spec: Spec = Default::default();\n    l"
  },
  {
    "path": "src/runtime/version.rs",
    "chars": 842,
    "preview": "use const_format::formatcp;\n\n/// API incompatible changes.\npub const VERSION_MAJOR: u32 = 1;\n\n/// Changing functionality"
  },
  {
    "path": "src/runtime/vm.rs",
    "chars": 3086,
    "preview": "use crate::error::OciSpecError;\nuse derive_builder::Builder;\nuse getset::{Getters, Setters};\nuse serde::{Deserialize, Se"
  },
  {
    "path": "src/runtime/windows.rs",
    "chars": 8477,
    "preview": "use crate::error::OciSpecError;\nuse derive_builder::Builder;\nuse getset::{CopyGetters, Getters, Setters};\nuse serde::{De"
  },
  {
    "path": "src/runtime/zos.rs",
    "chars": 2604,
    "preview": "use crate::error::OciSpecError;\nuse derive_builder::Builder;\nuse getset::{CopyGetters, Getters, Setters};\nuse serde::{De"
  },
  {
    "path": "test/data/artifact_manifest.json",
    "chars": 616,
    "preview": "{\n  \"mediaType\": \"application/vnd.oci.artifact.manifest.v1+json\",\n  \"artifactType\": \"application/vnd.example.sbom.v1\",\n "
  },
  {
    "path": "test/data/config.json",
    "chars": 1228,
    "preview": "{\n  \"created\": \"2015-10-31T22:22:56.015925234Z\",\n  \"author\": \"Alyssa P. Hacker <alyspdev@example.com>\",\n  \"architecture\""
  },
  {
    "path": "test/data/index.json",
    "chars": 591,
    "preview": "{\n  \"schemaVersion\": 2,\n  \"manifests\": [\n    {\n      \"mediaType\": \"application/vnd.oci.image.manifest.v1+json\",\n      \"d"
  },
  {
    "path": "test/data/manifest.json",
    "chars": 798,
    "preview": "{\n  \"schemaVersion\": 2,\n  \"config\": {\n    \"mediaType\": \"application/vnd.oci.image.config.v1+json\",\n    \"digest\": \"sha256"
  },
  {
    "path": "test/data/oci-layout",
    "chars": 41,
    "preview": "{\n  \"imageLayoutVersion\": \"lorem ipsum\"\n}"
  }
]

About this extraction

This page contains the full source code of the containers/oci-spec-rs GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 67 files (410.3 KB), approximately 105.4k tokens, and a symbol index with 449 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!